/* Optional realtime avatar. No paid session is created by loading the page. */ (()=>{ 'use strict'; if(document.documentElement.classList.contains('content-reader'))return; const player=window.AvatarScene,stage=document.querySelector('#stage');if(!player||!stage)return; const base=new URL('../',document.currentScript.src).pathname; let configured=false,connection=null,sequence=0,sdkPromise=null,settling=Promise.resolve(); fetch(base+'api/avatar/config',{credentials:'same-origin',cache:'no-store'}).then(r=>r.ok?r.json():null).then(d=>{configured=d?.enabled===true}).catch(()=>{}); function sdk(){ if(window.AliRtcEngine)return Promise.resolve(); if(!sdkPromise)sdkPromise=new Promise((resolve,reject)=>{ const script=document.createElement('script');script.src='https://g.alicdn.com/apsara-media-box/imp-web-rtc/7.1.9/aliyun-rtc-sdk.js'; script.onload=()=>window.AliRtcEngine?resolve():reject(Error('RTC unavailable'));script.onerror=reject;document.head.append(script); }); return sdkPromise; } function send(c,obj){if(c.ws?.readyState!==1)throw Error('Avatar connection closed');c.ws.send(JSON.stringify(obj))} function rejectTurn(c){if(c.turn&&!c.turn.cancelled){c.turn.cancelled=true;c.turn.error?.()}} function leave(c){ try{c.engine?.subscribeAllRemoteAudioStreams(false)}catch{} if(c.entry){settling=Promise.all([settling,c.disposed]);player.releaseLive(c.entry);c.entry=null} else c.container?.remove(); } function stop(reason='user'){ const c=connection;if(!c)return;connection=null;c.closed=true; clearTimeout(c.timeout);clearInterval(c.heartbeat);rejectTurn(c); try{send(c,{type:'stop'})}catch{} // Keep the socket briefly for the server's terminal confirmation, without creating any new task. c.closeTimer=setTimeout(()=>{try{c.ws?.close()}catch{}},35000); leave(c); } function broken(c){ if(c.closed)return; rejectTurn(c);stop('error'); } async function join(c,rtc){ await sdk();if(c.closed)return; if(!await AliRtcEngine.isSupported())throw Error('RTC unsupported'); const video=document.createElement('video');video.playsInline=true;video.muted=true;video.tabIndex=-1; video.setAttribute('aria-label','知微实时数字人');video.setAttribute('disablepictureinpicture',''); const container=document.createElement('div');container.className='realtime-scene';container.setAttribute('aria-hidden','true');container.append(video);stage.prepend(container); c.container=container;c.video=video; const engine=AliRtcEngine.getInstance();c.engine=engine; c.disposed=new Promise(resolve=>{c.resolveDisposed=resolve}); // The connection owns this entry while RTC is joining, before it occupies a scene slot. const entry=player.create({slot:'realtime',live:true,keep:true,video,muted:true, onEnter(){ if(c.closed)return; stage.classList.add('realtime-scene-visible');container.classList.add('is-active'); c.visible=true;engine.subscribeAllRemoteAudioStreams(!c.turn?.paused); clearTimeout(c.timeout);flush(c); }, onLeave(){stage.classList.remove('realtime-scene-visible');container.classList.remove('is-active');c.visible=false;engine.subscribeAllRemoteAudioStreams(false)}, onError(){broken(c)},onBlocked(){broken(c)}, dispose(){Promise.resolve(engine.leaveChannel()).catch(()=>{}).finally(()=>{try{engine.destroy()}catch{}c.resolveDisposed()});video.srcObject=null;container.remove()} }); c.entry=entry; engine.on('videoSubscribeStateChanged',(uid,old,next)=>{ if(c.closed||connection!==c)return; if(next===3){engine.setRemoteViewConfig(video,uid,1);player.set('realtime',entry)} }); for(const event of ['occurError','bye','remoteAudioPlayError','remoteVideoPlayError'])engine.on(event,()=>broken(c)); for(const event of ['remoteAudioAutoPlayFail','remoteVideoAutoPlayFail'])engine.on(event,()=>{ const t=c.turn;if(t&&!t.cancelled){t.blocked=true;t.paused=true;if(t.started)send(c,{type:'pause',paused:true});t.change?.()} }); await engine.publishLocalAudioStream(false);await engine.publishLocalVideoStream(false); engine.setDefaultSubscribeAllRemoteAudioStreams(false);engine.setDefaultSubscribeAllRemoteVideoStreams(true); if(c.closed)return; await engine.joinChannel(rtc.token,rtc.user_id); if(c.closed){try{await engine.leaveChannel()}catch{}} } function prepare(){ if(!configured)return false; if(connection&&!connection.closed)return true; const c={ws:null,engine:null,entry:null,visible:false,closed:false,turn:null};connection=c; c.timeout=setTimeout(()=>broken(c),80000); const u=new URL(base+'api/avatar/stream',location.href);u.protocol=location.protocol==='https:'?'wss:':'ws:'; sdk().then(async()=>{ if(c.closed)return; if(!await AliRtcEngine.isSupported())throw Error('RTC unsupported'); await settling;if(c.closed)return; c.ws=new WebSocket(u);c.ws.binaryType='arraybuffer'; c.ws.onopen=()=>{if(c.closed)return;send(c,{type:'start'});c.heartbeat=setInterval(()=>{try{send(c,{type:'ping'})}catch{broken(c)}},2000)}; c.ws.onmessage=event=>{ try{ const d=JSON.parse(event.data); if(d.type==='stopped'){ clearTimeout(c.closeTimer);c.closed=true;if(connection===c)connection=null;clearTimeout(c.timeout);clearInterval(c.heartbeat); if(c.turn&&!c.turn.drained)rejectTurn(c);leave(c);c.ws.close();return; } if(c.closed)return; if(d.type==='ready')join(c,d.rtc).catch(()=>broken(c)); else if(d.type==='drained'&&c.turn?.number===d.turn){ const t=c.turn;clearTimeout(t.tail);t.tail=setTimeout(()=>{if(c.turn===t&&!t.cancelled){t.drained=true;t.change?.()}},1500); }else if(d.type==='error')broken(c); }catch{broken(c)} }; c.ws.onerror=()=>broken(c);c.ws.onclose=()=>{clearInterval(c.heartbeat);if(!c.closed)broken(c)}; }).catch(()=>broken(c)); return true; } function flush(c){ const t=c.turn;if(!t||t.cancelled||!c.visible||t.paused||c.closed)return; try{ if(!t.started){send(c,{type:'turn',turn:t.number,text:t.request});t.started=true} while(t.events.length){ if(c.ws.bufferedAmount>1048576){setTimeout(()=>flush(c),40);return} const event=t.events.shift(); if(event instanceof Uint8Array){ for(let i=0;i16*1024*1024)throw Error('PCM limit');t.events.push(new Uint8Array(data).slice());flush(c)}, text(text){if(!t.cancelled){t.events.push({type:'text',text});flush(c)}}, finish(){if(!t.cancelled){t.events.push({type:'finish',bytes:t.bytes});flush(c)}}, pause(paused){t.paused=paused;t.blocked=false;if(t.started&&!c.closed)send(c,{type:'pause',paused});if(c.engine)c.engine.subscribeAllRemoteAudioStreams(!paused);if(paused)player.pause(c.entry);else{player.resume(c.entry);for(const m of document.querySelectorAll('audio,video'))if(m.srcObject&&![...m.srcObject.getTracks()].some(x=>x.kind==='video'&&m!==c.video)){m.play().catch(()=>{t.blocked=true;t.change?.()})}flush(c)}}, cancel(){if(t.cancelled)return;t.cancelled=true;clearTimeout(t.tail);t.events=[];if(t.started&&!c.closed){try{send(c,{type:'interrupt'})}catch{}}} }; flush(c);return handle; } window.addEventListener('pagehide',()=>stop('pagehide')); window.addEventListener('zhiwei-other-playback',()=>stop('other_media')); window.ZhiweiAvatarBridge=Object.freeze({prepare,begin,stop, question(){const c=connection;if(c&&!c.closed&&c.ws?.readyState===1)send(c,{type:'question'})}, isEnabled:()=>configured,isConnected:()=>!!connection&&!connection.closed}); })();