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