@3dsource/angular-unreal-module 0.0.159 → 0.0.161

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- "use strict";(()=>{"use strict";const g=/metabox-configurator\/(modular|basic)\//i,m="3dsource:region-ping",k="assets/config.json",f="https://datacenter.3dsource.com/signallingserver",w="42e01f";function l(){return typeof crypto!="undefined"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,o=>{const e=Math.random()*16|0;return(o==="x"?e:e&3|8).toString(16)})}function y(o){return o.replace("http://","ws://").replace("https://","wss://")}const h="[stream-prefetch]",r=(...o)=>console.log(h,...o),c=(...o)=>console.warn(h,...o),u=(...o)=>console.error(h,...o);class b{constructor(){const e=document.currentScript||(()=>{const s=document.getElementsByTagName("script");return s[s.length-1]})(),t=e&&e.dataset||{};this.configUrl=t.configUrl||k,this.fallbackPollingUrl=t.fallbackUrl||"",this.skipRouteCheck=this.boolOr(t.skipRouteCheck,!1),this.config={ws_timeout:this.numberOr(t.wsTimeout,6e3),poll_timeout:this.numberOr(t.pollTimeout,4e3)};const i=new URLSearchParams(window.location.search);this.forceTURN=i.has("ForceTURN"),this.forceMaxBundle=i.has("ForceMaxBundle"),this.forceMonoAudio=i.has("forceMono"),this.ids={clientId:l(),viewId:l(),correlationId:l()},this.region="",this.pc=null,this.dc=null,this.stream=null,this.ws=null,this.remoteDescriptionSet=!1,this.iceBuffer=[],this.wsTimeoutId=null,this.settled=!1}run(){if(!(typeof window=="undefined"||!("WebSocket"in window)||!("RTCPeerConnection"in window))){if(this.skipRouteCheck){c("skipRouteCheck=true \u2014 ignoring URL gate; using fallback polling"),this.publishParked("connecting"),this.connect().catch(e=>this.fail("exception",e));return}if(!g.test(window.location.href)){r("skip \u2014 not a metabox-configurator/{modular|basic} route");return}r("start",{ids:this.ids}),this.publishParked("connecting"),this.connect().catch(e=>this.fail("exception",e))}}connect(){return this.mark("prefetch_start"),this.region=this.readRegion()||"",r("region from cache:",this.region||"(none)"),(this.skipRouteCheck?Promise.resolve(null):this.fetchConfig()).then(t=>{let i;if(t){if(i=this.chooseEndpoint(t),i&&!this.isAllowedEndpoint(i.url)){r("skip \u2014 signalling URL not sanctioned for prefetch",i.url),this.fail("endpoint-not-allowed");return}}else if(this.fallbackPollingUrl)c("no app config \u2014 falling back to polling",this.fallbackPollingUrl),i={type:"polling",url:this.fallbackPollingUrl};else{this.fail("no-config");return}if(!i){this.fail("no-endpoint");return}r("endpoint chosen",{type:i.type,url:i.url});const s=this.adaptUrlToRegion(i.url);return this.pollOrchestration(s).then(n=>{if(!n){this.fail("no-ws-url");return}return this.parked.machineId=n,r("opening socket",n),this.openSocket(n)})})}fetchConfig(){const e=new URL(this.configUrl,window.location.origin).href;return this.fetchWithTimeout(e,{credentials:"omit"},this.config.poll_timeout).then(t=>t.ok?t.json().catch(()=>null):null).catch(()=>null)}chooseEndpoint(e){const t=e.signalling,i=this.firstVersionUrl(t&&t.versions);return i?{type:"polling",url:i}:null}isAllowedEndpoint(e){if(typeof e!="string")return!1;const t=e.replace(/\/+$/,"");return t===f||t.startsWith(f+"/")}firstVersionUrl(e){if(!e||typeof e!="object")return"";for(const t of Object.keys(e))if(e[t])return e[t];return""}pollOrchestration(e){const t=`${e}${this.ids.clientId}/${this.ids.viewId}`;return this.fetchWithTimeout(t,{credentials:"omit"},this.config.poll_timeout).then(i=>i.ok?i.json().catch(()=>({})):null).then(i=>i&&i.signallingServer?y(`${window.location.protocol}//${i.signallingServer}`):null).catch(()=>null)}adaptUrlToRegion(e){return this.region&&e.indexOf(":region")===-1?e.replace(/signallingserver/gi,`signallingserver/region:${this.region}`):e}openSocket(e){const t=new WebSocket(e);t.binaryType="arraybuffer",this.ws=t,this.parked.ws=t,this.wsTimeoutId=setTimeout(()=>{t.readyState!==WebSocket.OPEN&&this.fail("ws-timeout")},this.config.ws_timeout),t.onopen=()=>{clearTimeout(this.wsTimeoutId),this.mark("ws_open"),r("WS open \u2014 waiting for config",{region:this.region})},t.onmessage=i=>this.onWsMessage(i),t.onerror=i=>{u("WS error",i),this.fail("ws-error")},t.onclose=i=>{!this.settled&&this.parked.state!=="ready"&&(c("WS closed before ready",{code:i&&i.code}),this.fail("ws-closed"))}}onWsMessage(e){let t;try{const i=typeof e.data=="string"?e.data:null;if(i===null)return;t=JSON.parse(i)}catch{return}switch(r("WS \u2190",t.type),t.type){case"ssInfo":this.parked.ssData=t.data;break;case"config":this.onConfig(t);break;case"offer":this.onOffer(t);break;case"answer":this.onAnswer(t);break;case"iceCandidate":this.onIceCandidate(t.candidate);break;case"ping":this.send({type:"pong",source:"front"});break;case"streamerDisconnected":case"playerDisconnected":this.fail("streamer-disconnected");break;case"error":this.fail("orchestration-error");break;default:break}}onConfig(e){if(this.pc)return;const t=Object.assign({},e.peerConnectionOptions);this.forceTURN&&(t.iceTransportPolicy="relay"),t.bundlePolicy=this.forceMaxBundle?"max-bundle":"balanced",t.iceCandidatePoolSize=1,this.parked.config=t;const i=new RTCPeerConnection(t);this.pc=i,this.parked.pc=i,r("PeerConnection created from config"),i.onicecandidate=s=>{s.candidate&&this.send({type:"iceCandidate",candidate:s.candidate})},i.ondatachannel=s=>this.wireDataChannel(s.channel),i.ontrack=s=>{s.track&&s.track.kind==="video"&&s.streams[0]&&(r("video track received"),this.stream=s.streams[0],this.parked.stream=this.stream,this.maybeReady())}}onOffer(e){return this.pc?(this.remoteDescriptionSet=!1,this.iceBuffer.length=0,this.pc.setRemoteDescription(e).then(()=>(this.remoteDescriptionSet=!0,this.flushIce(),this.pc.createAnswer())).then(t=>(t.sdp=this.mungeSDP(t.sdp||""),this.pc.setLocalDescription(t).then(()=>{r("WS \u2192 answer sent"),this.send(t)}))).catch(t=>this.fail("offer-failed",t))):Promise.resolve()}onAnswer(e){return this.pc?this.pc.setRemoteDescription(e).then(()=>{this.remoteDescriptionSet=!0,this.flushIce()}).catch(t=>this.fail("answer-failed",t)):Promise.resolve()}onIceCandidate(e){e&&(this.forceTURN&&String(e.candidate).indexOf("relay")<0||(this.remoteDescriptionSet&&this.pc?this.pc.addIceCandidate(e).catch(()=>{}):this.iceBuffer.push(e)))}flushIce(){for(const e of this.iceBuffer)this.pc.addIceCandidate(e).catch(()=>{});this.iceBuffer.length=0}wireDataChannel(e){e.binaryType="arraybuffer",this.dc=e,this.parked.dc=e,e.onmessage=t=>{this.parked.dcBuffer.push(t.data)},e.onopen=()=>{r("DataChannel open"),this.mark("dc_open"),this.maybeReady()},e.onclose=()=>{!this.settled&&this.parked.state!=="ready"&&(c("DataChannel closed before ready"),this.fail("dc-closed"))},e.readyState==="open"&&this.maybeReady()}maybeReady(){this.settled||!this.dc||this.dc.readyState!=="open"||this.stream&&(this.settled=!0,this.mark("ready"),this.parked.state="ready",r("READY \u2014 parked on window.__stream",this.parked.timings),this.fireSettled())}publishParked(e){const t=this;this.parked={state:e,pc:null,dc:null,stream:null,ws:null,ssData:void 0,machineId:void 0,config:void 0,ids:this.ids,dcBuffer:[],timings:{},adopt(){return t.clearTimers(),this.state="adopted",this},onSettled(i){if(typeof i=="function"){if(this.state!=="connecting"){const s=this.state;Promise.resolve().then(()=>i(s));return}t.settledCb=i}}},window.__stream=this.parked}fireSettled(){const e=this.settledCb;if(e){this.settledCb=null;try{e(this.parked.state)}catch{}}}clearTimers(){this.wsTimeoutId&&(clearTimeout(this.wsTimeoutId),this.wsTimeoutId=null)}fail(e,t){if(!(this.parked&&(this.parked.state==="adopted"||this.parked.state==="failed"))){u("FAILED:",e,t||""),this.settled=!0,this.clearTimers();try{this.dc&&this.dc.close()}catch{}try{this.pc&&this.pc.close()}catch{}try{this.ws&&this.ws.close()}catch{}this.parked&&(this.parked.state="failed",this.parked.failReason=e,t&&(this.parked.failError=String(t&&t.message||t)),this.fireSettled())}}mungeSDP(e){let t=this.prioritizeH264Profile(e,w);t=t.replace(/(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n/gm,`$1;x-google-start-bitrate=10000;x-google-max-bitrate=100000\r
2
- `);let i="maxaveragebitrate=510000;";return i+=this.forceMonoAudio?"stereo=0;":"stereo=1;",i+="useinbandfec=1",t=t.replace("useinbandfec=1",i),t}prioritizeH264Profile(e,t){const i=e.split(`\r
3
- `),s=[];for(const n of i)if(n.startsWith("a=fmtp:")&&n.toLowerCase().includes(`profile-level-id=${t}`)){const a=n.split(":")[1]&&n.split(":")[1].split(" ")[0];a&&s.push(a)}return s.length===0?e:i.map(n=>{if(!n.startsWith("m=video"))return n;const a=n.split(" "),T=a.slice(0,3),p=a.slice(3),x=[...s.filter(d=>p.includes(d)),...p.filter(d=>!s.includes(d))];return[...T,...x].join(" ")}).join(`\r
4
- `)}send(e){const t=typeof e=="string"?e:JSON.stringify(e);if(this.ws&&this.ws.readyState===WebSocket.OPEN)try{this.ws.send(t)}catch{}}mark(e){this.parked&&(this.parked.timings[e]=performance.now())}readRegion(){try{const e=localStorage.getItem(m);if(!e)return null;const t=JSON.parse(e);return!t||typeof t.region_code!="string"||typeof t.ts!="number"||Date.now()-t.ts>=2592e6?null:t.region_code}catch{return null}}fetchWithTimeout(e,t,i){const s=new AbortController;t.signal=s.signal;const n=setTimeout(()=>s.abort(),i);return fetch(e,t).finally(()=>clearTimeout(n))}numberOr(e,t){const i=Number(e);return e!=null&&e!==""&&!Number.isNaN(i)?i:t}boolOr(e,t){return e==="true"||e===""?!0:e==="false"?!1:t}}new b().run()})();
1
+ "use strict";(()=>{"use strict";const g=/metabox-configurator\/(modular|basic)\//i,m="features",k="3dsource:region-ping",w="assets/config.json",f="https://datacenter.3dsource.com/signallingserver",y="42e01f";function l(){return typeof crypto!="undefined"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,o=>{const t=Math.random()*16|0;return(o==="x"?t:t&3|8).toString(16)})}function b(o){return o.replace("http://","ws://").replace("https://","wss://")}const h="[stream-prefetch]",n=(...o)=>console.log(h,...o),c=(...o)=>console.warn(h,...o),u=(...o)=>console.error(h,...o);class T{constructor(){const t=document.currentScript||(()=>{const s=document.getElementsByTagName("script");return s[s.length-1]})(),e=t&&t.dataset||{};this.configUrl=e.configUrl||w,this.fallbackPollingUrl=e.fallbackUrl||"",this.skipRouteCheck=this.boolOr(e.skipRouteCheck,!1),this.config={ws_timeout:this.numberOr(e.wsTimeout,6e3),poll_timeout:this.numberOr(e.pollTimeout,4e3)};const i=new URLSearchParams(window.location.search);this.forceTURN=i.has("ForceTURN"),this.forceMaxBundle=i.has("ForceMaxBundle"),this.forceMonoAudio=i.has("forceMono"),this.ids={clientId:l(),viewId:l(),correlationId:l()},this.region="",this.pc=null,this.dc=null,this.stream=null,this.ws=null,this.remoteDescriptionSet=!1,this.iceBuffer=[],this.wsTimeoutId=null,this.settled=!1}run(){if(!(typeof window=="undefined"||!("WebSocket"in window)||!("RTCPeerConnection"in window))){if(this.isNewOrchestrationEnabled()){n("skip \u2014 features.newOrchestration is enabled");return}if(this.skipRouteCheck){c("skipRouteCheck=true \u2014 ignoring URL gate; using fallback polling"),this.publishParked("connecting"),this.connect().catch(t=>this.fail("exception",t));return}if(!g.test(window.location.href)){n("skip \u2014 not a metabox-configurator/{modular|basic} route");return}n("start",{ids:this.ids}),this.publishParked("connecting"),this.connect().catch(t=>this.fail("exception",t))}}connect(){return this.mark("prefetch_start"),this.region=this.readRegion()||"",n("region from cache:",this.region||"(none)"),(this.skipRouteCheck?Promise.resolve(null):this.fetchConfig()).then(e=>{let i;if(e){if(i=this.chooseEndpoint(e),i&&!this.isAllowedEndpoint(i.url)){n("skip \u2014 signalling URL not sanctioned for prefetch",i.url),this.fail("endpoint-not-allowed");return}}else if(this.fallbackPollingUrl)c("no app config \u2014 falling back to polling",this.fallbackPollingUrl),i={type:"polling",url:this.fallbackPollingUrl};else{this.fail("no-config");return}if(!i){this.fail("no-endpoint");return}n("endpoint chosen",{type:i.type,url:i.url});const s=this.adaptUrlToRegion(i.url);return this.pollOrchestration(s).then(r=>{if(!r){this.fail("no-ws-url");return}return this.parked.machineId=r,n("opening socket",r),this.openSocket(r)})})}fetchConfig(){const t=new URL(this.configUrl,window.location.origin).href;return this.fetchWithTimeout(t,{credentials:"omit"},this.config.poll_timeout).then(e=>e.ok?e.json().catch(()=>null):null).catch(()=>null)}chooseEndpoint(t){const e=t.signalling,i=this.firstVersionUrl(e&&e.versions);return i?{type:"polling",url:i}:null}isAllowedEndpoint(t){if(typeof t!="string")return!1;const e=t.replace(/\/+$/,"");return e===f||e.startsWith(f+"/")}firstVersionUrl(t){if(!t||typeof t!="object")return"";for(const e of Object.keys(t))if(t[e])return t[e];return""}pollOrchestration(t){const e=`${t}${this.ids.clientId}/${this.ids.viewId}`;return this.fetchWithTimeout(e,{credentials:"omit"},this.config.poll_timeout).then(i=>i.ok?i.json().catch(()=>({})):null).then(i=>i&&i.signallingServer?b(`${window.location.protocol}//${i.signallingServer}`):null).catch(()=>null)}adaptUrlToRegion(t){return this.region&&t.indexOf(":region")===-1?t.replace(/signallingserver/gi,`signallingserver/region:${this.region}`):t}openSocket(t){const e=new WebSocket(t);e.binaryType="arraybuffer",this.ws=e,this.parked.ws=e,this.wsTimeoutId=setTimeout(()=>{e.readyState!==WebSocket.OPEN&&this.fail("ws-timeout")},this.config.ws_timeout),e.onopen=()=>{clearTimeout(this.wsTimeoutId),this.mark("ws_open"),n("WS open \u2014 waiting for config",{region:this.region})},e.onmessage=i=>this.onWsMessage(i),e.onerror=i=>{u("WS error",i),this.fail("ws-error")},e.onclose=i=>{!this.settled&&this.parked.state!=="ready"&&(c("WS closed before ready",{code:i&&i.code}),this.fail("ws-closed"))}}onWsMessage(t){let e;try{const i=typeof t.data=="string"?t.data:null;if(i===null)return;e=JSON.parse(i)}catch{return}switch(n("WS \u2190",e.type),e.type){case"ssInfo":this.parked.ssData=e.data;break;case"config":this.onConfig(e);break;case"offer":this.onOffer(e);break;case"answer":this.onAnswer(e);break;case"iceCandidate":this.onIceCandidate(e.candidate);break;case"ping":this.send({type:"pong",source:"front"});break;case"streamerDisconnected":case"playerDisconnected":this.fail("streamer-disconnected");break;case"error":this.fail("orchestration-error");break;default:break}}onConfig(t){if(this.pc)return;const e=Object.assign({},t.peerConnectionOptions);this.forceTURN&&(e.iceTransportPolicy="relay"),e.bundlePolicy=this.forceMaxBundle?"max-bundle":"balanced",e.iceCandidatePoolSize=1,this.parked.config=e;const i=new RTCPeerConnection(e);this.pc=i,this.parked.pc=i,n("PeerConnection created from config"),i.onicecandidate=s=>{s.candidate&&this.send({type:"iceCandidate",candidate:s.candidate})},i.ondatachannel=s=>this.wireDataChannel(s.channel),i.ontrack=s=>{s.track&&s.track.kind==="video"&&s.streams[0]&&(n("video track received"),this.stream=s.streams[0],this.parked.stream=this.stream,this.maybeReady())}}onOffer(t){return this.pc?(this.remoteDescriptionSet=!1,this.iceBuffer.length=0,this.pc.setRemoteDescription(t).then(()=>(this.remoteDescriptionSet=!0,this.flushIce(),this.pc.createAnswer())).then(e=>(e.sdp=this.mungeSDP(e.sdp||""),this.pc.setLocalDescription(e).then(()=>{n("WS \u2192 answer sent"),this.send(e)}))).catch(e=>this.fail("offer-failed",e))):Promise.resolve()}onAnswer(t){return this.pc?this.pc.setRemoteDescription(t).then(()=>{this.remoteDescriptionSet=!0,this.flushIce()}).catch(e=>this.fail("answer-failed",e)):Promise.resolve()}onIceCandidate(t){t&&(this.forceTURN&&String(t.candidate).indexOf("relay")<0||(this.remoteDescriptionSet&&this.pc?this.pc.addIceCandidate(t).catch(()=>{}):this.iceBuffer.push(t)))}flushIce(){for(const t of this.iceBuffer)this.pc.addIceCandidate(t).catch(()=>{});this.iceBuffer.length=0}wireDataChannel(t){t.binaryType="arraybuffer",this.dc=t,this.parked.dc=t,t.onmessage=e=>{this.parked.dcBuffer.push(e.data)},t.onopen=()=>{n("DataChannel open"),this.mark("dc_open"),this.maybeReady()},t.onclose=()=>{!this.settled&&this.parked.state!=="ready"&&(c("DataChannel closed before ready"),this.fail("dc-closed"))},t.readyState==="open"&&this.maybeReady()}maybeReady(){this.settled||!this.dc||this.dc.readyState!=="open"||this.stream&&(this.settled=!0,this.mark("ready"),this.parked.state="ready",n("READY \u2014 parked on window.__stream",this.parked.timings),this.fireSettled())}publishParked(t){const e=this;this.parked={state:t,pc:null,dc:null,stream:null,ws:null,ssData:void 0,machineId:void 0,config:void 0,ids:this.ids,dcBuffer:[],timings:{},adopt(){return e.clearTimers(),this.state="adopted",this},onSettled(i){if(typeof i=="function"){if(this.state!=="connecting"){const s=this.state;Promise.resolve().then(()=>i(s));return}e.settledCb=i}}},window.__stream=this.parked}fireSettled(){const t=this.settledCb;if(t){this.settledCb=null;try{t(this.parked.state)}catch{}}}clearTimers(){this.wsTimeoutId&&(clearTimeout(this.wsTimeoutId),this.wsTimeoutId=null)}fail(t,e){if(!(this.parked&&(this.parked.state==="adopted"||this.parked.state==="failed"))){u("FAILED:",t,e||""),this.settled=!0,this.clearTimers();try{this.dc&&this.dc.close()}catch{}try{this.pc&&this.pc.close()}catch{}try{this.ws&&this.ws.close()}catch{}this.parked&&(this.parked.state="failed",this.parked.failReason=t,e&&(this.parked.failError=String(e&&e.message||e)),this.fireSettled())}}mungeSDP(t){let e=this.prioritizeH264Profile(t,y);e=e.replace(/(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n/gm,`$1;x-google-start-bitrate=10000;x-google-max-bitrate=100000\r
2
+ `);let i="maxaveragebitrate=510000;";return i+=this.forceMonoAudio?"stereo=0;":"stereo=1;",i+="useinbandfec=1",e=e.replace("useinbandfec=1",i),e}prioritizeH264Profile(t,e){const i=t.split(`\r
3
+ `),s=[];for(const r of i)if(r.startsWith("a=fmtp:")&&r.toLowerCase().includes(`profile-level-id=${e}`)){const a=r.split(":")[1]&&r.split(":")[1].split(" ")[0];a&&s.push(a)}return s.length===0?t:i.map(r=>{if(!r.startsWith("m=video"))return r;const a=r.split(" "),x=a.slice(0,3),p=a.slice(3),S=[...s.filter(d=>p.includes(d)),...p.filter(d=>!s.includes(d))];return[...x,...S].join(" ")}).join(`\r
4
+ `)}send(t){const e=typeof t=="string"?t:JSON.stringify(t);if(this.ws&&this.ws.readyState===WebSocket.OPEN)try{this.ws.send(e)}catch{}}mark(t){this.parked&&(this.parked.timings[t]=performance.now())}isNewOrchestrationEnabled(){try{const t=localStorage.getItem(m);if(!t)return!1;const e=JSON.parse(t);return!!(e&&e.newOrchestration===!0)}catch{return!1}}readRegion(){try{const t=localStorage.getItem(k);if(!t)return null;const e=JSON.parse(t);return!e||typeof e.region_code!="string"||typeof e.ts!="number"||Date.now()-e.ts>=2592e6?null:e.region_code}catch{return null}}fetchWithTimeout(t,e,i){const s=new AbortController;e.signal=s.signal;const r=setTimeout(()=>s.abort(),i);return fetch(t,e).finally(()=>clearTimeout(r))}numberOr(t,e){const i=Number(t);return t!=null&&t!==""&&!Number.isNaN(i)?i:e}boolOr(t,e){return t==="true"||t===""?!0:t==="false"?!1:e}}new T().run()})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3dsource/angular-unreal-module",
3
- "version": "0.0.159",
3
+ "version": "0.0.161",
4
4
  "description": "Angular integration for Unreal Engine Pixel Streaming",
5
5
  "keywords": [
6
6
  "3dsource",
@@ -389,12 +389,19 @@ declare const OrchestrationMessageTypes: {
389
389
  readonly p2pEstablished: "p2pEstablished";
390
390
  readonly instanceReserved: "instanceReserved";
391
391
  readonly playerDisconnected: "playerDisconnected";
392
- readonly streamPreparedForUserInteraction: "streamPreparedForUserInteraction";
393
392
  readonly streamerDisconnected: "streamerDisconnected";
394
393
  readonly interruptClientStream: "interruptClientStream";
395
394
  };
396
- interface InstanceReady extends InstanceMessageBase {
395
+ interface InstanceReady extends MessageBase {
397
396
  type: 'instanceReady';
397
+ correlationId: string;
398
+ payload: {
399
+ instanceId: string;
400
+ originInstanceId: string;
401
+ ipAddress: string;
402
+ streamRequestId: string;
403
+ originInstanceUri: string;
404
+ };
398
405
  }
399
406
  interface InstanceReserved extends InstanceMessageBase {
400
407
  type: 'instanceReserved';
@@ -464,10 +471,6 @@ interface P2PEstablishedMessage extends MessageBase {
464
471
  source: string;
465
472
  sessionId: string;
466
473
  }
467
- interface streamPreparedForUserInteractionMessage extends MessageBase {
468
- type: 'streamPreparedForUserInteraction';
469
- correlationId: string;
470
- }
471
474
  interface RequestStream extends MessageBase {
472
475
  type: 'requestStream';
473
476
  correlationId: string;
@@ -542,7 +545,6 @@ interface SignalingMessageMap {
542
545
  [OrchestrationMessageTypes.p2pEstablished]: P2PEstablishedMessage;
543
546
  [OrchestrationMessageTypes.instanceReserved]: InstanceReserved;
544
547
  [OrchestrationMessageTypes.playerDisconnected]: ForceClientDisconnect;
545
- [OrchestrationMessageTypes.streamPreparedForUserInteraction]: streamPreparedForUserInteractionMessage;
546
548
  [OrchestrationMessageTypes.streamerDisconnected]: ForceClientDisconnect;
547
549
  [OrchestrationMessageTypes.interruptClientStream]: InterruptClientStream;
548
550
  }
@@ -1115,6 +1117,18 @@ declare class ConsoleExtensionsService extends SubService {
1115
1117
  private readonly unrealConfig;
1116
1118
  constructor();
1117
1119
  protected init(): void;
1120
+ /**
1121
+ * Hammer the reconnect paths: repeatedly drop the DataChannel / PeerConnection
1122
+ * (and rapid combos) and assert the stream recovers each time. Recovery is
1123
+ * measured off the store's `dataChannelConnected` flag (the source of truth,
1124
+ * regardless of transport flow — old relay or new stream socket).
1125
+ *
1126
+ * Console: await stressTest() · await stressTest({ iterations: 100 })
1127
+ */
1128
+ private stressTest;
1129
+ /** Resolve true once `dataChannelConnected` reaches `target`, or false on timeout. */
1130
+ private waitDataChannel;
1131
+ private sleep;
1118
1132
  private unrealHelp;
1119
1133
  static ɵfac: i0.ɵɵFactoryDeclaration<ConsoleExtensionsService, never>;
1120
1134
  static ɵprov: i0.ɵɵInjectableDeclaration<ConsoleExtensionsService>;
@@ -1306,6 +1320,10 @@ declare class SignallingService extends SubService {
1306
1320
  onWebRtcAnswer$: Subject<RTCSessionDescriptionInit | null>;
1307
1321
  abort$: Observable<_ngrx_store.Action<string>>;
1308
1322
  private ws;
1323
+ private streamWs;
1324
+ private streamMachineConnected;
1325
+ private streamMachineRetryCount;
1326
+ private streamWsSub?;
1309
1327
  orchestrationRetrySub?: Subscription;
1310
1328
  private wsMsgHandlers;
1311
1329
  protected correlationId: string;
@@ -1361,6 +1379,8 @@ declare class SignallingService extends SubService {
1361
1379
  private pollLoop;
1362
1380
  private connectToCirrus;
1363
1381
  private addWsHandlers;
1382
+ private onOrchestrationClose;
1383
+ private onOrchestrationError;
1364
1384
  /**
1365
1385
  * Adopt a live signalling socket opened by the `stream-prefetch.js`
1366
1386
  * head-script. The socket is already OPEN and `requestStream` was already
@@ -1370,18 +1390,54 @@ declare class SignallingService extends SubService {
1370
1390
  * `correlationId` so subsequent correlated messages line up.
1371
1391
  */
1372
1392
  adoptSocket(ws: WebSocket, correlationId: string): void;
1393
+ /**
1394
+ * Open the SECOND socket, directly to the stream machine's Cirrus
1395
+ * (`originInstanceUri` from `instanceReady`). The whole WebRTC signalling
1396
+ * (config/offer/answer/iceCandidate/ping) flows over this socket; the
1397
+ * orchestration socket (`this.ws`) stays open for control/lifecycle.
1398
+ *
1399
+ * "Just connect" — nothing is sent on open (the machine is already reserved
1400
+ * for this session): Cirrus pushes `config` and Unreal sends the `offer`.
1401
+ */
1402
+ connectToStreamMachine(originInstanceUri: string): void;
1403
+ /**
1404
+ * The stream-machine (Cirrus) connection failed. Re-request a machine over
1405
+ * the still-open orchestration socket: orchestration issues a new
1406
+ * streamRequestId and a fresh `instanceReady(originInstanceUri)`, which calls
1407
+ * {@link connectToStreamMachine} again on a different machine. Bounded by
1408
+ * {@link INNER_RETRY_LIMIT}; on exhaustion the stream is dropped.
1409
+ */
1410
+ private retryStreamMachine;
1411
+ private addStreamWsHandlers;
1412
+ private teardownStreamWs;
1373
1413
  private showStatusMessage;
1374
1414
  /**
1375
1415
  * Close the connection with the signaling server
1376
1416
  * @param data
1377
1417
  */
1378
1418
  close(data: CloseReason): void;
1419
+ private teardownSocket;
1379
1420
  send(data: AnySignalingMessage): void;
1421
+ /**
1422
+ * Send player signalling (offer/answer/iceCandidate/pong/p2pEstablished/
1423
+ * subscribe) over the ACTIVE signalling socket: the stream-machine socket in
1424
+ * the new flow (once connected), or the orchestration/relay socket in the old
1425
+ * polling flow (where there is no separate stream socket).
1426
+ */
1427
+ sendPlayerSignal(data: AnySignalingMessage): void;
1428
+ private sendTo;
1380
1429
  protected handleMessage(msg: MessageBase): void;
1381
1430
  private setHandlersFromStream;
1382
1431
  protected sendRequestStream(): void;
1383
1432
  /**
1384
- * Check if the WebSocket connection is open and ready.
1433
+ * Check if the ACTIVE player-signalling socket is open and ready. Mirrors
1434
+ * {@link sendPlayerSignal}'s socket choice so peer-reconnect gating is correct
1435
+ * in both flows:
1436
+ * - NEW flow (`streamMachineConnected`): the stream-machine socket
1437
+ * (`streamWs`). The orchestration socket (`this.ws`) is intentionally
1438
+ * closed after occupancy, so checking it would wrongly report "not
1439
+ * connected" and block reconnect.
1440
+ * - OLD polling flow: the single relay socket (`this.ws`) — unchanged.
1385
1441
  */
1386
1442
  isConnected(): boolean;
1387
1443
  private getRegion;
@@ -1687,7 +1743,23 @@ declare class VideoService extends SubService {
1687
1743
  videoStats$: rxjs.Observable<AggregatedStat>;
1688
1744
  private destroyAbort;
1689
1745
  constructor();
1746
+ /**
1747
+ * Sets (or replaces) the DOM target for the streaming `<video>`.
1748
+ *
1749
+ * Attaching is idempotent and order-independent on purpose: `create()` is
1750
+ * driven by the network (`config`, or a synchronous prefetch adoption) while
1751
+ * the container comes from the scene component's render pass, so either side
1752
+ * can win the race. If `create()` got there first the element is detached —
1753
+ * adopt it here instead of leaving it orphaned (which showed up as a white
1754
+ * scene with an empty `#player` and no `videoStreamInitialized`).
1755
+ */
1690
1756
  setContainer(container?: HTMLDivElement | null): void;
1757
+ /**
1758
+ * Moves `this.video` under the current container unless it is already there.
1759
+ * `parentElement` (not `isConnected`) is the guard: it also covers a swap to a
1760
+ * different container while the element sits in a still-live old one.
1761
+ */
1762
+ private attachVideoToContainer;
1691
1763
  setLatencyTimings(latencyTimings: string): void;
1692
1764
  setEncoder(data: ArrayBuffer): void;
1693
1765
  create(): void;
@@ -1695,8 +1767,31 @@ declare class VideoService extends SubService {
1695
1767
  attachAudioStream(stream: MediaStream): void;
1696
1768
  play(): void;
1697
1769
  safePlay(video: HTMLVideoElement | null): Promise<void>;
1770
+ /**
1771
+ * Retries playback on the next real user gesture — the only thing that lifts
1772
+ * a browser autoplay block. Shared by `safePlay()` and `playAudio()` so both
1773
+ * media elements use one arming/cleanup path.
1774
+ *
1775
+ * Listeners are `once` + `capture` and bound to `destroyAbort.signal`, so a
1776
+ * `destroy()` in this session drops them instead of leaking into the next one.
1777
+ */
1778
+ private armGestureRetry;
1698
1779
  private getStats;
1699
1780
  private generateAggregatedStatsFunction;
1781
+ /**
1782
+ * Folds one `inbound-rtp` video report into the accumulator: copies the raw
1783
+ * report, pins the per-session baselines on the first pass, then derives the
1784
+ * deltas relative to the previous pass.
1785
+ */
1786
+ private applyInboundVideoReport;
1787
+ /**
1788
+ * Resolves the transport's selected candidate pair into the local/remote
1789
+ * descriptors shown in the stats overlay. Silently skips when the pair (or
1790
+ * either of its candidates) is missing from this report.
1791
+ */
1792
+ private applySelectedPair;
1793
+ /** Per-session values that do not come from a single report. */
1794
+ private applySessionTotals;
1700
1795
  private onAggregatedStats;
1701
1796
  private destroy;
1702
1797
  private changeStatusMainVideoOnScene;
@@ -2192,7 +2287,7 @@ declare function IceCandidateHandler(this: SignallingService, msg: {
2192
2287
  candidate: RTCIceCandidate;
2193
2288
  }): void;
2194
2289
 
2195
- declare function InstanceReadyHandler(this: SignallingService): void;
2290
+ declare function InstanceReadyHandler(this: SignallingService, msg: InstanceReady): void;
2196
2291
 
2197
2292
  declare function InstanceReservedHandler(this: SignallingService, msg: InstanceReserved): void;
2198
2293
 
@@ -2565,9 +2660,23 @@ declare const setOrchestrationParameters: _ngrx_store.ActionCreator<string, (pro
2565
2660
  /**
2566
2661
  * Dispatched when the orchestration server signals that the reserved
2567
2662
  * instance is ready to serve the stream (`instanceReady` WS message).
2568
- * Emitted for observability only — no reducer state change.
2663
+ * Carries the stream-machine coordinates — `originInstanceUri` is used to
2664
+ * open the second (stream-machine) signalling socket. Also consumed by
2665
+ * telemetry as a phase marker.
2569
2666
  */
2570
- declare const instanceReady: _ngrx_store.ActionCreator<string, () => _ngrx_store.Action<string>>;
2667
+ declare const instanceReady: _ngrx_store.ActionCreator<string, (props: {
2668
+ instanceId: string;
2669
+ originInstanceId: string;
2670
+ ipAddress: string;
2671
+ streamRequestId: string;
2672
+ originInstanceUri: string;
2673
+ }) => {
2674
+ instanceId: string;
2675
+ originInstanceId: string;
2676
+ ipAddress: string;
2677
+ streamRequestId: string;
2678
+ originInstanceUri: string;
2679
+ } & _ngrx_store.Action<string>>;
2571
2680
  /**
2572
2681
  * Dispatched when `RegionsPingService.getFastest()` begins — right
2573
2682
  * before the region-list HTTP request goes out. Bookends the entire
@@ -3112,4 +3221,4 @@ declare const selectUserInactivityDetected: _ngrx_store.MemoizedSelector<Record<
3112
3221
  declare const selectIsVideoPlayingAndDataChannelConnected: _ngrx_store.MemoizedSelector<Record<string, any>, boolean, (s1: boolean, s2: boolean) => boolean>;
3113
3222
 
3114
3223
  export { AFKService, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DESTROY_DISCONNECT_REASONS, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, INNER_RETRY_LIMIT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, LatencyTimings, LoadingProgressService, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RECONNECT_CONFIG, REGION_PING_ACCURATE_SAMPLES, REGION_PING_BACKGROUND_REFRESH_DELAY_MS, REGION_PING_CACHE_KEY, REGION_PING_CACHE_TTL_MS, REGION_PING_WARMUP_DEADLINE_MS, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamAdoptionService, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UNREAL_CONFIG, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealSceneComponent, UnrealStatusMessage, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_RETRY_PER_INSTANCE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeStatusMainVideoOnScene, changeStreamResolutionAction, commandCompleted, commandStarted, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, floatToPercents, floatToSmoothPercents, forceResizeViewport, fromResizeObserver, getActiveUrl, getImageFromVideoStream, getResizeStudioValues, getResizeValuesUniversal, getRtcErrorMessage, iceConnectionFailed, initSignalling, instanceReady, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, provideUnrealState, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsFullyConnected, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectUserInactivityDetected, selectWarnTimeout, setAfkTimerCountdown, setAfkTimerHide, setAfkTimerVisible, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMaxFps, setOrchestrationContext, setOrchestrationMessage, setOrchestrationParameters, setOrchestrationProgress, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setUnrealPlaywrightConfig, setUserInactivityTimeout, setViewportNotReady, setViewportReady, showLoadingProgress, showResumeStreamPopup, showUnrealErrorMessage, showUserInactivityTimeoutPopup, signalingRetryAttempted, smoothTransition, startStream, streamAdopted, trackMixpanelEvent, trackTelemetryData, uint8ToBase64, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
3115
- export type { AggregatedStat, AnySignalingMessage, AwsInstance, BootstrapInfo, CandidateMessage, CloseReason, CommandInProgress, ConfigMessage, ConnectionError, ControlSchemeValues, DisconnectReasonType, EmittedMilestone, EventId, ForceClientDisconnect, FpsLowData, FreezeFrameMessage, InboundVideoStats, InputProps, InstanceReady, InstanceReserved, InterruptClientStream, ListStreamersMessage, MessageBase, MessageTypeValues, OrchestrationErrorMessage, OrchestrationMessageType, P2PEstablishedMessage, ParkedStream, PhaseEndedData, PhaseOverlay, PhaseParam, PingMessage, PlayerCountMessage, PollingOrchestrationMessage, Quality, QualityAnomalyData, ReceivedFile, RequestStream, ResourceBucket, ResourceTotals, SSInfo, SampleData, SelectedIceCandidatePair, SessionEndReason, SessionEndedData, SessionStartedData, SignalingData, SignalingMessageHandler, SignalingMessageMap, SignalingRetryData, SignalingRetryKind, SlowResource, StreamConfig, StreamRequestContext, StreamResolutionProps, StreamTelemetryEvent, StreamTelemetryEventMap, StreamTelemetryEventName, StreamTelemetryLifecycleData, StreamTelemetryLifecycleEventName, StreamTelemetryTrackData, StreamerListMessage, TelemetryEventEnvelope, TelemetryLifecycleEnvelope, TelemetryType, UnknownFields, UnrealCallbackDescriptor, UnrealCallbackEventMap, UnrealCallbackJson, UnrealCallbackJsonWrapped, UnrealInitialConfig, UnrealState, WSCloseCodesValues, WindowAggregate, streamPreparedForUserInteractionMessage };
3224
+ export type { AggregatedStat, AnySignalingMessage, AwsInstance, BootstrapInfo, CandidateMessage, CloseReason, CommandInProgress, ConfigMessage, ConnectionError, ControlSchemeValues, DisconnectReasonType, EmittedMilestone, EventId, ForceClientDisconnect, FpsLowData, FreezeFrameMessage, InboundVideoStats, InputProps, InstanceReady, InstanceReserved, InterruptClientStream, ListStreamersMessage, MessageBase, MessageTypeValues, OrchestrationErrorMessage, OrchestrationMessageType, P2PEstablishedMessage, ParkedStream, PhaseEndedData, PhaseOverlay, PhaseParam, PingMessage, PlayerCountMessage, PollingOrchestrationMessage, Quality, QualityAnomalyData, ReceivedFile, RequestStream, ResourceBucket, ResourceTotals, SSInfo, SampleData, SelectedIceCandidatePair, SessionEndReason, SessionEndedData, SessionStartedData, SignalingData, SignalingMessageHandler, SignalingMessageMap, SignalingRetryData, SignalingRetryKind, SlowResource, StreamConfig, StreamRequestContext, StreamResolutionProps, StreamTelemetryEvent, StreamTelemetryEventMap, StreamTelemetryEventName, StreamTelemetryLifecycleData, StreamTelemetryLifecycleEventName, StreamTelemetryTrackData, StreamerListMessage, TelemetryEventEnvelope, TelemetryLifecycleEnvelope, TelemetryType, UnknownFields, UnrealCallbackDescriptor, UnrealCallbackEventMap, UnrealCallbackJson, UnrealCallbackJsonWrapped, UnrealInitialConfig, UnrealState, WSCloseCodesValues, WindowAggregate };