冯杨

websocket断链重连,不限制连接次数;tts合成语音,角色声音更换一种;socket,csn源路径修改;音频处理控件引入,是搭配Fay控制器传入音频的配置。

@@ -45,8 +45,6 @@ import asyncio @@ -45,8 +45,6 @@ import asyncio
45 import torch 45 import torch
46 from typing import Dict 46 from typing import Dict
47 from logger import logger 47 from logger import logger
48 -from pydub import AudioSegment  
49 -from io import BytesIO  
50 48
51 49
52 app = Flask(__name__) 50 app = Flask(__name__)
@@ -55,7 +53,7 @@ nerfreals:Dict[int, BaseReal] = {} #sessionid:BaseReal @@ -55,7 +53,7 @@ nerfreals:Dict[int, BaseReal] = {} #sessionid:BaseReal
55 opt = None 53 opt = None
56 model = None 54 model = None
57 avatar = None 55 avatar = None
58 - 56 +
59 57
60 #####webrtc############################### 58 #####webrtc###############################
61 pcs = set() 59 pcs = set()
@@ -95,7 +93,7 @@ async def offer(request): @@ -95,7 +93,7 @@ async def offer(request):
95 nerfreals[sessionid] = None 93 nerfreals[sessionid] = None
96 nerfreal = await asyncio.get_event_loop().run_in_executor(None, build_nerfreal,sessionid) 94 nerfreal = await asyncio.get_event_loop().run_in_executor(None, build_nerfreal,sessionid)
97 nerfreals[sessionid] = nerfreal 95 nerfreals[sessionid] = nerfreal
98 - 96 +
99 pc = RTCPeerConnection() 97 pc = RTCPeerConnection()
100 pcs.add(pc) 98 pcs.add(pc)
101 99
@@ -144,7 +142,7 @@ async def human(request): @@ -144,7 +142,7 @@ async def human(request):
144 if params['type']=='echo': 142 if params['type']=='echo':
145 nerfreals[sessionid].put_msg_txt(params['text']) 143 nerfreals[sessionid].put_msg_txt(params['text'])
146 elif params['type']=='chat': 144 elif params['type']=='chat':
147 - res=await asyncio.get_event_loop().run_in_executor(None, llm_response, params['text'],nerfreals[sessionid]) 145 + res=await asyncio.get_event_loop().run_in_executor(None, llm_response, params['text'],nerfreals[sessionid])
148 #nerfreals[sessionid].put_msg_txt(res) 146 #nerfreals[sessionid].put_msg_txt(res)
149 147
150 return web.Response( 148 return web.Response(
@@ -153,13 +151,14 @@ async def human(request): @@ -153,13 +151,14 @@ async def human(request):
153 {"code": 0, "data":"ok"} 151 {"code": 0, "data":"ok"}
154 ), 152 ),
155 ) 153 )
156 - 154 +from pydub import AudioSegment
  155 +from io import BytesIO
157 async def humanaudio(request): 156 async def humanaudio(request):
158 try: 157 try:
159 params = await request.json() 158 params = await request.json()
160 sessionid = int(params.get('sessionid', 0)) 159 sessionid = int(params.get('sessionid', 0))
161 fileobj = params.get('file_url') 160 fileobj = params.get('file_url')
162 - 161 +
163 # 获取音频文件数据 162 # 获取音频文件数据
164 if isinstance(fileobj, str) and fileobj.startswith("http"): 163 if isinstance(fileobj, str) and fileobj.startswith("http"):
165 async with aiohttp.ClientSession() as session: 164 async with aiohttp.ClientSession() as session:
@@ -177,20 +176,20 @@ async def humanaudio(request): @@ -177,20 +176,20 @@ async def humanaudio(request):
177 filename = fileobj.filename 176 filename = fileobj.filename
178 filebytes = fileobj.file.read() 177 filebytes = fileobj.file.read()
179 is_mp3 = filename.lower().endswith('.mp3') 178 is_mp3 = filename.lower().endswith('.mp3')
180 - 179 +
181 if is_mp3: 180 if is_mp3:
182 audio = AudioSegment.from_file(BytesIO(filebytes), format="mp3") 181 audio = AudioSegment.from_file(BytesIO(filebytes), format="mp3")
183 out_io = BytesIO() 182 out_io = BytesIO()
184 audio.export(out_io, format="wav") 183 audio.export(out_io, format="wav")
185 filebytes = out_io.getvalue() 184 filebytes = out_io.getvalue()
186 - 185 +
187 nerfreals[sessionid].put_audio_file(filebytes) 186 nerfreals[sessionid].put_audio_file(filebytes)
188 187
189 return web.Response( 188 return web.Response(
190 content_type="application/json", 189 content_type="application/json",
191 text=json.dumps({"code": 0, "msg": "ok"}) 190 text=json.dumps({"code": 0, "msg": "ok"})
192 ) 191 )
193 - 192 +
194 except Exception as e: 193 except Exception as e:
195 return web.Response( 194 return web.Response(
196 content_type="application/json", 195 content_type="application/json",
@@ -200,7 +199,7 @@ async def humanaudio(request): @@ -200,7 +199,7 @@ async def humanaudio(request):
200 async def set_audiotype(request): 199 async def set_audiotype(request):
201 params = await request.json() 200 params = await request.json()
202 201
203 - sessionid = params.get('sessionid',0) 202 + sessionid = params.get('sessionid',0)
204 nerfreals[sessionid].set_custom_state(params['audiotype'],params['reinit']) 203 nerfreals[sessionid].set_custom_state(params['audiotype'],params['reinit'])
205 204
206 return web.Response( 205 return web.Response(
@@ -275,7 +274,7 @@ async def run(push_url,sessionid): @@ -275,7 +274,7 @@ async def run(push_url,sessionid):
275 await pc.setRemoteDescription(RTCSessionDescription(sdp=answer,type='answer')) 274 await pc.setRemoteDescription(RTCSessionDescription(sdp=answer,type='answer'))
276 ########################################## 275 ##########################################
277 # os.environ['MKL_SERVICE_FORCE_INTEL'] = '1' 276 # os.environ['MKL_SERVICE_FORCE_INTEL'] = '1'
278 -# os.environ['MULTIPROCESSING_METHOD'] = 'forkserver' 277 +# os.environ['MULTIPROCESSING_METHOD'] = 'forkserver'
279 if __name__ == '__main__': 278 if __name__ == '__main__':
280 mp.set_start_method('spawn') 279 mp.set_start_method('spawn')
281 parser = argparse.ArgumentParser() 280 parser = argparse.ArgumentParser()
@@ -291,7 +290,7 @@ if __name__ == '__main__': @@ -291,7 +290,7 @@ if __name__ == '__main__':
291 290
292 ### training options 291 ### training options
293 parser.add_argument('--ckpt', type=str, default='data/pretrained/ngp_kf.pth') 292 parser.add_argument('--ckpt', type=str, default='data/pretrained/ngp_kf.pth')
294 - 293 +
295 parser.add_argument('--num_rays', type=int, default=4096 * 16, help="num rays sampled per image for each training step") 294 parser.add_argument('--num_rays', type=int, default=4096 * 16, help="num rays sampled per image for each training step")
296 parser.add_argument('--cuda_ray', action='store_true', help="use CUDA raymarching instead of pytorch") 295 parser.add_argument('--cuda_ray', action='store_true', help="use CUDA raymarching instead of pytorch")
297 parser.add_argument('--max_steps', type=int, default=16, help="max num steps sampled per ray (only valid when using --cuda_ray)") 296 parser.add_argument('--max_steps', type=int, default=16, help="max num steps sampled per ray (only valid when using --cuda_ray)")
@@ -309,7 +308,7 @@ if __name__ == '__main__': @@ -309,7 +308,7 @@ if __name__ == '__main__':
309 308
310 ### network backbone options 309 ### network backbone options
311 parser.add_argument('--fp16', action='store_true', help="use amp mixed precision training") 310 parser.add_argument('--fp16', action='store_true', help="use amp mixed precision training")
312 - 311 +
313 parser.add_argument('--bg_img', type=str, default='white', help="background image") 312 parser.add_argument('--bg_img', type=str, default='white', help="background image")
314 parser.add_argument('--fbg', action='store_true', help="frame-wise bg") 313 parser.add_argument('--fbg', action='store_true', help="frame-wise bg")
315 parser.add_argument('--exp_eye', action='store_true', help="explicitly control the eyes") 314 parser.add_argument('--exp_eye', action='store_true', help="explicitly control the eyes")
@@ -423,11 +422,11 @@ if __name__ == '__main__': @@ -423,11 +422,11 @@ if __name__ == '__main__':
423 with open(opt.customvideo_config,'r') as file: 422 with open(opt.customvideo_config,'r') as file:
424 opt.customopt = json.load(file) 423 opt.customopt = json.load(file)
425 424
426 - if opt.model == 'ernerf': 425 + if opt.model == 'ernerf':
427 from nerfreal import NeRFReal,load_model,load_avatar 426 from nerfreal import NeRFReal,load_model,load_avatar
428 model = load_model(opt) 427 model = load_model(opt)
429 - avatar = load_avatar(opt)  
430 - 428 + avatar = load_avatar(opt)
  429 +
431 # we still need test_loader to provide audio features for testing. 430 # we still need test_loader to provide audio features for testing.
432 # for k in range(opt.max_session): 431 # for k in range(opt.max_session):
433 # opt.sessionid=k 432 # opt.sessionid=k
@@ -437,8 +436,8 @@ if __name__ == '__main__': @@ -437,8 +436,8 @@ if __name__ == '__main__':
437 from musereal import MuseReal,load_model,load_avatar,warm_up 436 from musereal import MuseReal,load_model,load_avatar,warm_up
438 logger.info(opt) 437 logger.info(opt)
439 model = load_model() 438 model = load_model()
440 - avatar = load_avatar(opt.avatar_id)  
441 - warm_up(opt.batch_size,model) 439 + avatar = load_avatar(opt.avatar_id)
  440 + warm_up(opt.batch_size,model)
442 # for k in range(opt.max_session): 441 # for k in range(opt.max_session):
443 # opt.sessionid=k 442 # opt.sessionid=k
444 # nerfreal = MuseReal(opt,audio_processor,vae, unet, pe,timesteps) 443 # nerfreal = MuseReal(opt,audio_processor,vae, unet, pe,timesteps)
@@ -496,7 +495,6 @@ if __name__ == '__main__': @@ -496,7 +495,6 @@ if __name__ == '__main__':
496 pagename='rtcpushapi.html' 495 pagename='rtcpushapi.html'
497 logger.info('start http server; http://<serverip>:'+str(opt.listenport)+'/'+pagename) 496 logger.info('start http server; http://<serverip>:'+str(opt.listenport)+'/'+pagename)
498 logger.info('如果使用webrtc,推荐访问webrtc集成前端: http://<serverip>:'+str(opt.listenport)+'/dashboard.html') 497 logger.info('如果使用webrtc,推荐访问webrtc集成前端: http://<serverip>:'+str(opt.listenport)+'/dashboard.html')
499 -  
500 def run_server(runner): 498 def run_server(runner):
501 loop = asyncio.new_event_loop() 499 loop = asyncio.new_event_loop()
502 asyncio.set_event_loop(loop) 500 asyncio.set_event_loop(loop)
@@ -90,7 +90,7 @@ class BaseTTS: @@ -90,7 +90,7 @@ class BaseTTS:
90 ########################################################################################### 90 ###########################################################################################
91 class EdgeTTS(BaseTTS): 91 class EdgeTTS(BaseTTS):
92 def txt_to_audio(self,msg): 92 def txt_to_audio(self,msg):
93 - voicename = "zh-CN-YunxiaNeural" 93 + voicename = "zh-CN-XiaoxiaoNeural"
94 text,textevent = msg 94 text,textevent = msg
95 t = time.time() 95 t = time.time()
96 asyncio.new_event_loop().run_until_complete(self.__main(voicename,text)) 96 asyncio.new_event_loop().run_until_complete(self.__main(voicename,text))
@@ -284,6 +284,7 @@ @@ -284,6 +284,7 @@
284 <h1 class="text-center mb-4">livetalking数字人交互平台</h1> 284 <h1 class="text-center mb-4">livetalking数字人交互平台</h1>
285 </div> 285 </div>
286 </div> 286 </div>
  287 + <input type="hidden" id="username" value="User">
287 288
288 <div class="row"> 289 <div class="row">
289 <!-- 视频区域 --> 290 <!-- 视频区域 -->
@@ -450,6 +451,154 @@ @@ -450,6 +451,154 @@
450 } 451 }
451 } 452 }
452 453
  454 + var ws;
  455 + var reconnectInterval = 5000; // 初始重连间隔为5秒
  456 + var reconnectAttempts = 0;
  457 + var maxReconnectInterval = 60000; // 最大重连间隔为60秒
  458 + var isReconnecting = false; // 标记是否正在重连中
  459 +
  460 + function generateUsername() {
  461 + var username = 'User';
  462 + // + Math.floor(Math.random() * 10000)
  463 + return username;
  464 + }
  465 +
  466 + function setUsername() {
  467 + var storedUsername = localStorage.getItem('username');
  468 + if (!storedUsername) {
  469 + storedUsername = generateUsername();
  470 + localStorage.setItem('username', storedUsername);
  471 + }
  472 + $('#username').val(storedUsername); // Use the username as the session ID
  473 + }
  474 +
  475 + setUsername();
  476 + function connectWebSocket() {
  477 + var host = window.location.hostname;
  478 + ws = new WebSocket("ws://127.0.0.1:10002");
  479 + ws.onopen = function() {
  480 + console.log('Connected to WebSocket on port 10002');
  481 + reconnectAttempts = 0; // 重置重连次数
  482 + reconnectInterval = 5000; // 重置重连间隔
  483 + updateConnectionStatus('connected');
  484 +
  485 + // 在连接成功后发送 {"Username": "User"}
  486 + var loginMessage = JSON.stringify({ "Username": $('#username').val() });
  487 + ws.send(loginMessage);
  488 + loginMessage = JSON.stringify({ "Output": "1" });
  489 + ws.send(loginMessage);
  490 + };
  491 +
  492 +
  493 + function addMessage(text, type = "right") {
  494 + const chatOverlay = document.getElementById("chatOverlay");
  495 + const messageDiv = document.createElement("div");
  496 + messageDiv.classList.add("message", type);
  497 +
  498 + const avatar = document.createElement("img");
  499 + avatar.classList.add("avatar");
  500 + avatar.src = type === "right" ? "images/avatar-right.png" : "images/avatar-left.png";
  501 +
  502 + const textContainer = document.createElement("div");
  503 + textContainer.classList.add("text-container");
  504 +
  505 + const textElement = document.createElement("div");
  506 + textElement.classList.add("text");
  507 + textElement.textContent = text;
  508 +
  509 + const timeElement = document.createElement("div");
  510 + timeElement.classList.add("time");
  511 + timeElement.textContent = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  512 +
  513 + textContainer.appendChild(textElement);
  514 + textContainer.appendChild(timeElement);
  515 +
  516 + messageDiv.appendChild(avatar);
  517 + messageDiv.appendChild(textContainer);
  518 +
  519 + chatOverlay.appendChild(messageDiv);
  520 +
  521 + // 自动滚动到底部
  522 + chatOverlay.scrollTop = chatOverlay.scrollHeight;
  523 + }
  524 +
  525 + ws.onmessage = function(e) {
  526 + console.log('WebSocket原始消息(dashboard.html):', e.data);
  527 + var messageData = JSON.parse(e.data);
  528 +
  529 + if (messageData.Data && messageData.Data.Key) {
  530 + if(messageData.Data.Key == "audio"){
  531 + var value = messageData.Data.HttpValue;
  532 + console.log('Value:', value);
  533 + fetch('/humanaudio', {
  534 + body: JSON.stringify({
  535 + file_url: value,
  536 + sessionid:parseInt(document.getElementById('sessionid').value),
  537 + }),
  538 + headers: {
  539 + 'Content-Type': 'application/json'
  540 + },
  541 + method: 'POST'
  542 + });
  543 + }else if (messageData.Data.Key == "text") {
  544 + var reply = messageData.Data.Value;
  545 + addMessage(reply, "left");
  546 + }
  547 + }
  548 +
  549 + };
  550 +
  551 + ws.onclose = function(e) {
  552 + console.log('WebSocket connection closed');
  553 + attemptReconnect();
  554 + };
  555 +
  556 + ws.onerror = function(e) {
  557 + console.error('WebSocket error:', e);
  558 + ws.close(); // 关闭连接并尝试重连
  559 + };
  560 + }
  561 +
  562 + function attemptReconnect() {
  563 + if (isReconnecting) return; // 防止多次重连
  564 +
  565 + isReconnecting = true;
  566 + reconnectAttempts++;
  567 +
  568 + // 使用指数退避算法计算下一次重连间隔
  569 + var currentInterval = Math.min(reconnectInterval * Math.pow(1.5, reconnectAttempts - 1), maxReconnectInterval);
  570 +
  571 + console.log('Attempting to reconnect... (Attempt ' + reconnectAttempts + ', 间隔: ' + currentInterval/1000 + '秒)');
  572 + updateConnectionStatus('connecting');
  573 +
  574 + if(document.getElementById('is_open') && parseInt(document.getElementById('is_open').value) == 1){
  575 + stop()
  576 + }
  577 +
  578 + setTimeout(function() {
  579 + isReconnecting = false;
  580 + connectWebSocket();
  581 + }, currentInterval);
  582 + }
  583 +
  584 + // 初始化 WebSocket 连接
  585 + connectWebSocket();
  586 +
  587 + // 添加页面可见性变化监听,当页面从隐藏变为可见时尝试重连
  588 + document.addEventListener('visibilitychange', function() {
  589 + if (document.visibilityState === 'visible') {
  590 + // 页面变为可见状态,检查WebSocket连接
  591 + if (!ws || ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {
  592 + console.log('页面可见,检测到WebSocket未连接,尝试重连...');
  593 + updateConnectionStatus('connecting');
  594 + // 重置重连计数和间隔,立即尝试重连
  595 + reconnectAttempts = 0;
  596 + reconnectInterval = 5000;
  597 + connectWebSocket();
  598 + }
  599 + }
  600 + });
  601 +
453 // 添加聊天消息 602 // 添加聊天消息
454 function addChatMessage(message, type = 'user') { 603 function addChatMessage(message, type = 'user') {
455 const messagesContainer = $('#chat-messages'); 604 const messagesContainer = $('#chat-messages');
@@ -578,9 +727,9 @@ @@ -578,9 +727,9 @@
578 e.preventDefault(); 727 e.preventDefault();
579 var message = $('#chat-message').val(); 728 var message = $('#chat-message').val();
580 if (!message.trim()) return; 729 if (!message.trim()) return;
581 - 730 +
582 console.log('Sending chat message:', message); 731 console.log('Sending chat message:', message);
583 - 732 +
584 // fetch('/human', { 733 // fetch('/human', {
585 // body: JSON.stringify({ 734 // body: JSON.stringify({
586 // text: message, 735 // text: message,
@@ -593,14 +742,14 @@ @@ -593,14 +742,14 @@
593 // }, 742 // },
594 // method: 'POST' 743 // method: 'POST'
595 // }); 744 // });
596 - 745 +
597 var payload = { 746 var payload = {
598 "model": "fay-streaming", 747 "model": "fay-streaming",
599 "messages": [ 748 "messages": [
600 - {  
601 - "role": $('#username').val(),  
602 - "content": message  
603 - } 749 + {
  750 + "role": $('#username').val(),
  751 + "content": message
  752 + }
604 ], 753 ],
605 "observation": "" 754 "observation": ""
606 }; 755 };
@@ -608,14 +757,14 @@ @@ -608,14 +757,14 @@
608 fetch('http://127.0.0.1:5000/v1/chat/completions', { 757 fetch('http://127.0.0.1:5000/v1/chat/completions', {
609 body: JSON.stringify(payload), 758 body: JSON.stringify(payload),
610 headers: { 759 headers: {
611 - 'Content-Type': 'application/json' 760 + 'Content-Type': 'application/json'
612 }, 761 },
613 method: 'POST' 762 method: 'POST'
614 }).then(function(response) { 763 }).then(function(response) {
615 if (response.ok) { 764 if (response.ok) {
616 - console.log('Message sent to API.'); 765 + console.log('Message sent to API.');
617 } else { 766 } else {
618 - console.error('Failed to send message to API.'); 767 + console.error('Failed to send message to API.');
619 } 768 }
620 }).catch(function(error) { 769 }).catch(function(error) {
621 console.error('Error:', error); 770 console.error('Error:', error);
@@ -50,7 +50,7 @@ @@ -50,7 +50,7 @@
50 <input type="text" id="audiotype" value="0"> 50 <input type="text" id="audiotype" value="0">
51 51
52 <script src="client.js"></script> 52 <script src="client.js"></script>
53 -<script type="text/javascript" src="http://cdn.sockjs.org/sockjs-0.3.4.js"></script> 53 +<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/sockjs-client@1.5.1/dist/sockjs.min.js"></script>
54 <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script> 54 <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
55 </body> 55 </body>
56 <script type="text/javascript" charset="utf-8"> 56 <script type="text/javascript" charset="utf-8">
@@ -3,6 +3,8 @@ @@ -3,6 +3,8 @@
3 <head> 3 <head>
4 <meta charset="UTF-8"/> 4 <meta charset="UTF-8"/>
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6 + <link rel="icon" href="favicon.ico" type="image/x-icon">
  7 + <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
6 <title>WebRTC 数字人</title> 8 <title>WebRTC 数字人</title>
7 <style> 9 <style>
8 body { 10 body {
@@ -276,7 +278,7 @@ @@ -276,7 +278,7 @@
276 </div> 278 </div>
277 279
278 <script src="client.js"></script> 280 <script src="client.js"></script>
279 -<script type="text/javascript" src="http://cdn.sockjs.org/sockjs-0.3.4.js"></script> 281 +<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/sockjs-client@1.5.1/dist/sockjs.min.js"></script>
280 <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script> 282 <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
281 <script type="text/javascript" charset="utf-8"> 283 <script type="text/javascript" charset="utf-8">
282 284
@@ -387,17 +389,20 @@ @@ -387,17 +389,20 @@
387 389
388 // WebSocket connection to Fay digital avatar (port 10002) 390 // WebSocket connection to Fay digital avatar (port 10002)
389 var ws; 391 var ws;
390 - var reconnectInterval = 5000; // 每5秒尝试重连一次  
391 - var maxReconnectAttempts = 10; // 最大重连次数 392 + var reconnectInterval = 5000; // 初始重连间隔为5秒
392 var reconnectAttempts = 0; 393 var reconnectAttempts = 0;
  394 + var maxReconnectInterval = 60000; // 最大重连间隔为60秒
  395 + var isReconnecting = false; // 标记是否正在重连中
393 396
394 function generateUsername() { 397 function generateUsername() {
395 - var username = 'User' + Math.floor(Math.random() * 10000); 398 + var username = 'User';
  399 + // + Math.floor(Math.random() * 10000)
396 return username; 400 return username;
397 } 401 }
398 402
399 function setUsername() { 403 function setUsername() {
400 var storedUsername = localStorage.getItem('username'); 404 var storedUsername = localStorage.getItem('username');
  405 + // console.log("当前存有的username:"+storedUsername);
401 if (!storedUsername) { 406 if (!storedUsername) {
402 storedUsername = generateUsername(); 407 storedUsername = generateUsername();
403 localStorage.setItem('username', storedUsername); 408 localStorage.setItem('username', storedUsername);
@@ -411,7 +416,7 @@ @@ -411,7 +416,7 @@
411 ws.onopen = function() { 416 ws.onopen = function() {
412 console.log('Connected to WebSocket on port 10002'); 417 console.log('Connected to WebSocket on port 10002');
413 reconnectAttempts = 0; // 重置重连次数 418 reconnectAttempts = 0; // 重置重连次数
414 - 419 + reconnectInterval = 5000; // 重置重连间隔
415 // 在连接成功后发送 {"Username": "User"} 420 // 在连接成功后发送 {"Username": "User"}
416 var loginMessage = JSON.stringify({ "Username": $('#username').val() }); 421 var loginMessage = JSON.stringify({ "Username": $('#username').val() });
417 ws.send(loginMessage); 422 ws.send(loginMessage);
@@ -453,6 +458,7 @@ @@ -453,6 +458,7 @@
453 } 458 }
454 459
455 ws.onmessage = function(e) { 460 ws.onmessage = function(e) {
  461 + console.log('WebSocket原始消息(webrtcapi.html):', e.data);
456 var messageData = JSON.parse(e.data); 462 var messageData = JSON.parse(e.data);
457 463
458 if (messageData.Data && messageData.Data.Key) { 464 if (messageData.Data && messageData.Data.Key) {
@@ -489,22 +495,42 @@ @@ -489,22 +495,42 @@
489 } 495 }
490 496
491 function attemptReconnect() { 497 function attemptReconnect() {
492 - if (reconnectAttempts < maxReconnectAttempts) {  
493 - reconnectAttempts++;  
494 - console.log('Attempting to reconnect... (Attempt ' + reconnectAttempts + ')');  
495 - if(parseInt(document.getElementById('is_open').value) == 1){  
496 - stop()  
497 - }  
498 - setTimeout(function() {  
499 - connectWebSocket();  
500 - }, reconnectInterval);  
501 - } else {  
502 - console.error('Maximum reconnection attempts reached. Could not reconnect to WebSocket.'); 498 + if (isReconnecting) return; // 防止多次重连
  499 +
  500 + isReconnecting = true;
  501 + reconnectAttempts++;
  502 +
  503 + // 使用指数退避算法计算下一次重连间隔
  504 + var currentInterval = Math.min(reconnectInterval * Math.pow(1.5, reconnectAttempts - 1), maxReconnectInterval);
  505 +
  506 + console.log('Attempting to reconnect... (Attempt ' + reconnectAttempts + ', 间隔: ' + currentInterval/1000 + '秒)');
  507 +
  508 + if(document.getElementById('is_open') && parseInt(document.getElementById('is_open').value) == 1){
  509 + stop()
503 } 510 }
  511 +
  512 + setTimeout(function() {
  513 + isReconnecting = false;
  514 + connectWebSocket();
  515 + }, currentInterval);
504 } 516 }
505 517
506 // 初始化 WebSocket 连接 518 // 初始化 WebSocket 连接
507 connectWebSocket(); 519 connectWebSocket();
  520 +
  521 + // 添加页面可见性变化监听,当页面从隐藏变为可见时尝试重连
  522 + document.addEventListener('visibilitychange', function() {
  523 + if (document.visibilityState === 'visible') {
  524 + // 页面变为可见状态,检查WebSocket连接
  525 + if (!ws || ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {
  526 + console.log('页面可见,检测到WebSocket未连接,尝试重连...');
  527 + // 重置重连计数和间隔,立即尝试重连
  528 + reconnectAttempts = 0;
  529 + reconnectInterval = 5000;
  530 + connectWebSocket();
  531 + }
  532 + }
  533 + });
508 }); 534 });
509 </script> 535 </script>
510 </body> 536 </body>
@@ -51,7 +51,7 @@ @@ -51,7 +51,7 @@
51 </div> 51 </div>
52 52
53 <script src="client.js"></script> 53 <script src="client.js"></script>
54 -<script type="text/javascript" src="http://cdn.sockjs.org/sockjs-0.3.4.js"></script> 54 +<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/sockjs-client@1.5.1/dist/sockjs.min.js"></script>
55 <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script> 55 <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
56 </body> 56 </body>
57 <script type="text/javascript" charset="utf-8"> 57 <script type="text/javascript" charset="utf-8">