websocket断链重连,不限制连接次数;tts合成语音,角色声音更换一种;socket,csn源路径修改;音频处理控件引入,是搭配Fay控制器传入音频的配置。
Showing
6 changed files
with
222 additions
and
49 deletions
@@ -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"> |
-
Please register or login to post a comment