@3dsource/angular-unreal-module 0.0.174 → 0.0.177
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.
- package/README.md +20 -6
- package/fesm2022/3dsource-angular-unreal-module-config.mjs +10 -0
- package/fesm2022/3dsource-angular-unreal-module-config.mjs.map +1 -0
- package/fesm2022/3dsource-angular-unreal-module.mjs +125 -52
- package/fesm2022/3dsource-angular-unreal-module.mjs.map +1 -1
- package/js/stream-prefetch.js +4 -4
- package/package.json +5 -1
- package/types/3dsource-angular-unreal-module-config.d.ts +27 -0
- package/types/3dsource-angular-unreal-module.d.ts +158 -147
package/js/stream-prefetch.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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]",r=(...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(),streamRequestId:""},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()){r("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)){r("skip \u2014 not a metabox-configurator/{modular|basic} route");return}r("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()||"",r("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)){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,this.parked.pollingUrl=s,r("opening socket",n),this.openSocket(n)})})}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?null:(this.ids.streamRequestId=i.streamRequestId||"",b(`${window.location.protocol}//${i.signallingServer}`))).catch(()=>null)}adaptUrlToRegion(t){return this.region&&t.indexOf(":region")===-1?t.replace(/signallingserver/gi,`signallingserver/region:${this.region}`):t}openSocket(t){const e=this.ids.streamRequestId,i=e?t+(t.indexOf("?")===-1?"?":"&")+"streamRequestId="+encodeURIComponent(e):t,s=new WebSocket(i);s.binaryType="arraybuffer",this.ws=s,this.parked.ws=s,this.wsTimeoutId=setTimeout(()=>{s.readyState!==WebSocket.OPEN&&this.fail("ws-timeout")},this.config.ws_timeout),s.onopen=()=>{clearTimeout(this.wsTimeoutId),this.mark("ws_open"),r("WS open \u2014 waiting for config",{region:this.region,streamRequestId:e||"(none)"})},s.onmessage=n=>this.onWsMessage(n),s.onerror=n=>{u("WS error",n),this.fail("ws-error")},s.onclose=n=>{!this.settled&&this.parked.state!=="ready"&&(c("WS closed before ready",{code:n&&n.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(r("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;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,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(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(()=>{r("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=()=>{r("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",r("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,pollingUrl: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",
|
|
3
|
-
`),
|
|
4
|
-
`)}
|
|
1
|
+
"use strict";(()=>{"use strict";const p=/metabox-configurator\/modular\/([^/?#]+)/i,m="features",g="3dsource:region-ping",w="assets/config.json",I="assets/features.json",y="metaboxModularConfigurator",T=new Set(["insecure-endpoint","no-endpoint","no-config"]),k="42e01f";function R(a){return a.replace("http://","ws://").replace("https://","wss://")}const l="[stream-prefetch]",r=(...a)=>console.log(l,...a),c=(...a)=>console.warn(l,...a),d=(...a)=>console.error(l,...a);class S{constructor(){this.readOptions(),this.ids={clientId:"",viewId:"",correlationId:"",streamRequestId:""},this.configuratorId="",this.region="",this.newOrchestration=!1,this.environmentId="",this.controlWs=null,this.handedOverControlWs=null,this.orchestrationRetryId=null,this.instanceReadyTimeoutId=null,this.streamReadyTimeoutId=null,this.pc=null,this.dc=null,this.stream=null,this.ws=null,this.remoteDescriptionSet=!1,this.iceBuffer=[],this.wsTimeoutId=null,this.settled=!1,this.settledCbs=[]}readOptions(){const e=document.currentScript||(()=>{const n=document.getElementsByTagName("script");return n[n.length-1]})(),t=e&&e.dataset||{};this.configUrl=t.configUrl||w,this.featuresUrl=t.featuresUrl||I,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")}run(){if(!(typeof window=="undefined"||!("WebSocket"in window)||!("RTCPeerConnection"in window))){if(window.__stream){r("skip \u2014 this page already has a stream owner",{owner:window.__stream.owner||"prefetch"});return}if(this.configuratorId=this.resolveConfiguratorId(),!this.configuratorId){r("skip \u2014 not a metabox-configurator/modular route");return}this.ids.correlationId=this.configuratorId,r("start",{configuratorId:this.configuratorId}),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)"),Promise.all([this.fetchConfig(),this.fetchFeatures()]).then(([e,t])=>this.withConfig(e,t))}withConfig(e,t){if(this.newOrchestration=this.isNewOrchestrationEnabled(t),r("orchestration mode",{newOrchestration:this.newOrchestration}),!e){this.fail("no-config");return}return this.fetchConfigurator(e).then(i=>this.withConfigurator(e,i))}withConfigurator(e,t){if(!t){this.fail("no-configurator");return}if(this.deriveIdentity(t),!this.ids.clientId||!this.ids.viewId){this.fail("no-identity");return}r("identity from configurator cache",{clientId:this.ids.clientId,viewId:this.ids.viewId});const i=this.chooseEndpoint(e,t);if(!i||!i.url){this.fail("no-endpoint");return}if(!this.isAllowedEndpoint(i.url)){r("skip \u2014 endpoint is not encrypted",i.url),this.fail("insecure-endpoint");return}return r("endpoint chosen",i),i.type==="orchestration"?this.runOrchestration(i.url):this.runLegacy(i.url)}resolveConfiguratorId(){const e=p.exec(window.location.href);if(!e)return"";try{return decodeURIComponent(e[1])}catch{return e[1]}}fetchConfig(){return this.fetchJson(this.configUrl)}fetchFeatures(){return this.fetchJson(this.featuresUrl)}fetchJson(e){const t=new URL(e,window.location.origin).href;return this.fetchWithTimeout(t,{credentials:"omit"},this.config.poll_timeout).then(i=>i.ok?i.json().catch(()=>null):null).catch(()=>null)}isNewOrchestrationEnabled(e){const t=e&&typeof e=="object"?e:{},i=this.readFeatureOverrides();return("newOrchestration"in i?i.newOrchestration:t.newOrchestration)===!0}readFeatureOverrides(){try{const e=localStorage.getItem(m);if(!e)return{};const t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}fetchConfigurator(e){const t=e&&e.cdn;if(!t)return c("app config has no `cdn` \u2014 cannot read the configurator identity"),Promise.resolve(null);const i=`${String(t).replace(/\/+$/,"")}/cache/configurator/${encodeURIComponent(this.configuratorId)}.json`;return this.fetchWithTimeout(i,{credentials:"omit"},this.config.poll_timeout).then(n=>n.ok?n.json().catch(()=>null):null).then(n=>{const s=n&&n.data&&n.data.modularConfigurator;return s||(c("configurator cache response has no modularConfigurator",i),null)}).catch(()=>null)}deriveIdentity(e){const t=e.client&&e.client.id;this.ids.clientId=t?`${t}_configurator`:"",this.ids.viewId=e.id||""}chooseEndpoint(e,t){const i=this.newOrchestration?e.newSignalling:e.signalling;if(!i)return null;this.environmentId=i.environmentId||"";const n=this.versionKey(t),s=i.versions,o=n&&s&&s[n]||"";return o||c("no endpoint for Unreal version key",n||"(none)"),{type:this.newOrchestration?"orchestration":"polling",url:o}}versionKey(e){const i=(Array.isArray(e.components)?e.components.slice().sort((o,h)=>(o&&o.position)-(h&&h.position)):[])[0],n=i&&i.product&&i.product.model?i.product.model.unrealVersion:"";if(!n)return"";const s=String(n).split(".");return s.length<2?"":[s[0],s[1],"*"].join(".")}isAllowedEndpoint(e){if(typeof e!="string")return!1;let t;try{t=new URL(e,window.location.href)}catch{return!1}return t.protocol==="https:"||t.protocol==="wss:"?!0:t.protocol!=="http:"&&t.protocol!=="ws:"?!1:t.hostname==="localhost"||t.hostname==="127.0.0.1"||t.hostname==="[::1]"}readRegion(){try{const e=localStorage.getItem(g);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}}adaptUrlToRegion(e){return this.region&&e.indexOf(":region")===-1?e.replace(/signallingserver/gi,`signallingserver/region:${this.region}`):e}runLegacy(e){const t=this.adaptUrlToRegion(e);return this.pollOrchestration(t).then(i=>{if(!i){this.fail("no-ws-url");return}return this.parked.machineId=i,this.parked.pollingUrl=t,this.parked.kind="polling",r("opening socket",i),this.openSocket(i)})}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?null:(this.ids.streamRequestId=i.streamRequestId||"",this.parked.instanceName=i.signallingServer,R(`${window.location.protocol}//${i.signallingServer}`))).catch(()=>null)}runOrchestration(e){if(!this.environmentId)return this.fail("no-environment-id"),Promise.resolve();const t=this.adaptUrlToRegion(e);this.parked.pollingUrl=t;const i=new WebSocket(t);return this.controlWs=i,this.wsTimeoutId=setTimeout(()=>{i.readyState!==WebSocket.OPEN&&this.fail("control-ws-timeout")},this.config.ws_timeout),i.onopen=()=>{clearTimeout(this.wsTimeoutId),this.wsTimeoutId=null,this.mark("control_ws_open"),r("control socket open \u2014 requesting stream"),this.instanceReadyTimeoutId=setTimeout(()=>this.fail("instance-ready-timeout"),18e4),this.sendRequestStream()},i.onmessage=n=>this.guard("control-message-failed",()=>this.onControlMessage(n)),i.onerror=n=>{d("control WS error",n),this.fail("control-ws-error")},i.onclose=()=>{!this.settled&&!this.ws&&(c("control WS closed before instanceReady"),this.fail("control-ws-closed"))},Promise.resolve()}sendRequestStream(){this.sendControl({type:"requestStream",correlationId:this.ids.correlationId,payload:{region:this.region,environmentId:this.environmentId,context:{id:y,data:{configuratorId:this.configuratorId}}}})}sendControl(e){const t=this.controlWs;if(t&&t.readyState===WebSocket.OPEN)try{t.send(JSON.stringify(e))}catch{}}onControlMessage(e){var i;let t;try{const n=typeof e.data=="string"?e.data:null;if(n===null)return;t=JSON.parse(n)}catch{return}switch(r("control \u2190",t.type),t.type){case"instanceReserved":this.parked.eta=(i=t.payload&&t.payload.eta)!=null?i:null;break;case"instanceReady":this.onInstanceReady(t.payload||{});break;case"error":this.onOrchestrationError(t.payload||{});break;default:break}}onInstanceReady(e){if(this.ws)return;const t=e.originInstanceUri;if(!t){this.fail("no-instance-uri");return}clearTimeout(this.orchestrationRetryId),this.orchestrationRetryId=null,clearTimeout(this.instanceReadyTimeoutId),this.instanceReadyTimeoutId=null,this.ids.streamRequestId=e.streamRequestId||"",this.parked.machineId=t,this.parked.kind="socket",this.parked.instanceName=e.originInstanceId||null,this.mark("instance_ready"),r("instance ready \u2014 opening Cirrus socket",t),this.handOverControlWs(),this.openSocket(t)}onOrchestrationError(e){const t=e.code;if(t===4001||t===4002){c("orchestration refused the request, retrying",t),clearTimeout(this.orchestrationRetryId),this.orchestrationRetryId=setTimeout(()=>this.sendRequestStream(),this.config.poll_timeout);return}t!==4003&&this.fail("orchestration-error",e.message||String(t))}handOverControlWs(){const e=this.controlWs;e&&(this.controlWs=null,this.handedOverControlWs=e,this.parked.controlWs=e,e.onmessage=null,e.onerror=null,e.onclose=null,r("control socket handed over \u2014 left open for orchestration"))}closeControlWs(){const e=[this.controlWs,this.handedOverControlWs];this.controlWs=null,this.handedOverControlWs=null;for(const t of e)if(t){t.onopen=null,t.onmessage=null,t.onerror=null,t.onclose=null;try{t.close()}catch{}}}openSocket(e){const t=this.ids.streamRequestId,i=t?e+(e.indexOf("?")===-1?"?":"&")+"streamRequestId="+encodeURIComponent(t):e,n=new WebSocket(i);n.binaryType="arraybuffer",this.ws=n,this.parked.ws=n,this.wsTimeoutId=setTimeout(()=>{n.readyState!==WebSocket.OPEN&&this.fail("ws-timeout")},this.config.ws_timeout),n.onopen=()=>{clearTimeout(this.wsTimeoutId),this.wsTimeoutId=null,this.streamReadyTimeoutId=setTimeout(()=>this.fail("stream-ready-timeout"),6e4),this.mark("ws_open"),r("WS open \u2014 waiting for config",{region:this.region,streamRequestId:t||"(none)"})},n.onmessage=s=>this.guard("message-failed",()=>this.onWsMessage(s)),n.onerror=s=>{d("WS error",s),this.fail("ws-error")},n.onclose=s=>{!this.settled&&this.parked.state!=="ready"&&(c("WS closed before ready",{code:s&&s.code}),this.fail("ws-closed"))}}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{}}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;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=n=>{n.candidate&&this.send({type:"iceCandidate",candidate:n.candidate})},i.ondatachannel=n=>this.wireDataChannel(n.channel),i.ontrack=n=>{n.track&&n.track.kind==="video"&&n.streams[0]&&(r("video track received"),this.stream=n.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,clearTimeout(this.streamReadyTimeoutId),this.streamReadyTimeoutId=null,this.mark("ready"),this.parked.state="ready",r("READY \u2014 parked on window.__stream",this.parked.timings),this.fireSettled())}mungeSDP(e){let t=this.prioritizeH264Profile(e,k);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
|
+
`),n=[];for(const s of i)if(s.startsWith("a=fmtp:")&&s.toLowerCase().includes(`profile-level-id=${t}`)){const o=s.split(":")[1]&&s.split(":")[1].split(" ")[0];o&&n.push(o)}return n.length===0?e:i.map(s=>{if(!s.startsWith("m=video"))return s;const o=s.split(" "),h=o.slice(0,3),f=o.slice(3),O=[...n.filter(u=>f.includes(u)),...f.filter(u=>!n.includes(u))];return[...h,...O].join(" ")}).join(`\r
|
|
4
|
+
`)}publishParked(e){const t=this;this.parked={state:e,pc:null,dc:null,stream:null,ws:null,ssData:void 0,machineId:void 0,pollingUrl:void 0,kind:void 0,instanceName:void 0,eta:void 0,controlWs: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 n=this.state;Promise.resolve().then(()=>i(n));return}t.settledCbs.push(i)}}},window.__stream=this.parked}fireSettled(){const e=this.settledCbs;this.settledCbs=[];for(const t of e)try{t(this.parked.state)}catch{}}mark(e){this.parked&&(this.parked.timings[e]=performance.now())}clearTimers(){this.wsTimeoutId&&(clearTimeout(this.wsTimeoutId),this.wsTimeoutId=null),clearTimeout(this.orchestrationRetryId),this.orchestrationRetryId=null,clearTimeout(this.instanceReadyTimeoutId),this.instanceReadyTimeoutId=null,clearTimeout(this.streamReadyTimeoutId),this.streamReadyTimeoutId=null}fail(e,t){if(!(this.parked&&(this.parked.state==="adopted"||this.parked.state==="failed"))){(T.has(e)?r:d)("FAILED:",e,t||""),this.settled=!0,this.clearTimers(),this.closeControlWs();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())}}guard(e,t){try{t()}catch(i){this.fail(e,i)}}fetchWithTimeout(e,t,i){const n=new AbortController;t.signal=n.signal;const s=setTimeout(()=>n.abort(),i);return fetch(e,t).finally(()=>clearTimeout(s))}numberOr(e,t){const i=Number(e);return e!=null&&e!==""&&!Number.isNaN(i)?i:t}}new S().run()})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@3dsource/angular-unreal-module",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.177",
|
|
4
4
|
"description": "Angular integration for Unreal Engine Pixel Streaming",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"3dsource",
|
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
".": {
|
|
42
42
|
"types": "./types/3dsource-angular-unreal-module.d.ts",
|
|
43
43
|
"default": "./fesm2022/3dsource-angular-unreal-module.mjs"
|
|
44
|
+
},
|
|
45
|
+
"./config": {
|
|
46
|
+
"types": "./types/3dsource-angular-unreal-module-config.d.ts",
|
|
47
|
+
"default": "./fesm2022/3dsource-angular-unreal-module-config.mjs"
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
50
|
"type": "module"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which command protocol the module speaks.
|
|
5
|
+
*
|
|
6
|
+
* - `'metabox'` (default) — the full Metabox command protocol.
|
|
7
|
+
* - `'default'` — a stock Pixel Streaming application: the module sends none of
|
|
8
|
+
* its own `MetaBoxCommand` packets.
|
|
9
|
+
*/
|
|
10
|
+
type UnrealMode = 'metabox' | 'default';
|
|
11
|
+
interface UnrealInitialConfig {
|
|
12
|
+
mode?: UnrealMode;
|
|
13
|
+
playwright?: boolean;
|
|
14
|
+
customErrorsEndpoint?: string;
|
|
15
|
+
commandTelemetryReceiver?: string;
|
|
16
|
+
regionsPingUrl?: string;
|
|
17
|
+
screenLockerContainerId?: string;
|
|
18
|
+
dataChannelConnectionTimeout?: number;
|
|
19
|
+
streamTelemetryV2Url?: string;
|
|
20
|
+
fpsMonitor?: boolean;
|
|
21
|
+
autoHighResolution?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare const UNREAL_CONFIG: InjectionToken<UnrealInitialConfig>;
|
|
25
|
+
|
|
26
|
+
export { UNREAL_CONFIG };
|
|
27
|
+
export type { UnrealInitialConfig, UnrealMode };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as _3dsource_angular_unreal_module from '@3dsource/angular-unreal-module';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { Signal, TemplateRef, ElementRef,
|
|
3
|
+
import { Signal, TemplateRef, ElementRef, DestroyRef, PipeTransform } from '@angular/core';
|
|
4
|
+
import { UnrealInitialConfig } from '@3dsource/angular-unreal-module/config';
|
|
5
|
+
export { UNREAL_CONFIG, UnrealInitialConfig, UnrealMode } from '@3dsource/angular-unreal-module/config';
|
|
4
6
|
import * as _ngrx_store from '@ngrx/store';
|
|
5
7
|
import { Store } from '@ngrx/store';
|
|
6
8
|
import * as rxjs from 'rxjs';
|
|
@@ -354,27 +356,6 @@ interface FreezeFrameMessage {
|
|
|
354
356
|
progress: number | null;
|
|
355
357
|
}
|
|
356
358
|
|
|
357
|
-
/**
|
|
358
|
-
* Which command protocol the module speaks.
|
|
359
|
-
*
|
|
360
|
-
* - `'metabox'` (default) — the full Metabox command protocol.
|
|
361
|
-
* - `'default'` — a stock Pixel Streaming application: the module sends none of
|
|
362
|
-
* its own `MetaBoxCommand` packets.
|
|
363
|
-
*/
|
|
364
|
-
type UnrealMode = 'metabox' | 'default';
|
|
365
|
-
interface UnrealInitialConfig {
|
|
366
|
-
mode?: UnrealMode;
|
|
367
|
-
playwright?: boolean;
|
|
368
|
-
customErrorsEndpoint?: string;
|
|
369
|
-
commandTelemetryReceiver?: string;
|
|
370
|
-
regionsPingUrl?: string;
|
|
371
|
-
screenLockerContainerId?: string;
|
|
372
|
-
dataChannelConnectionTimeout?: number;
|
|
373
|
-
streamTelemetryV2Url?: string;
|
|
374
|
-
fpsMonitor?: boolean;
|
|
375
|
-
autoHighResolution?: boolean;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
359
|
interface SignalingData {
|
|
379
360
|
instanceType: string;
|
|
380
361
|
branchInfo: string;
|
|
@@ -385,11 +366,84 @@ interface SignalingData {
|
|
|
385
366
|
connectionId: string;
|
|
386
367
|
}
|
|
387
368
|
|
|
369
|
+
type Quality = 'lime' | 'red' | 'orange';
|
|
370
|
+
|
|
371
|
+
interface AggregatedStat {
|
|
372
|
+
quality: Quality;
|
|
373
|
+
aggregatedStats: InboundVideoStats;
|
|
374
|
+
}
|
|
375
|
+
interface InboundVideoStats extends RTCInboundRtpStreamStats {
|
|
376
|
+
VideoEncoderQP: number;
|
|
377
|
+
runTime: number;
|
|
378
|
+
pixelRatio: number;
|
|
379
|
+
bytesReceivedStart: number;
|
|
380
|
+
framesDecodedStart: number;
|
|
381
|
+
timestampStart: number;
|
|
382
|
+
videoCodec: string;
|
|
383
|
+
avgBitrate: number;
|
|
384
|
+
avgFrameRate: number;
|
|
385
|
+
selectedPair: {
|
|
386
|
+
local: {
|
|
387
|
+
type: string;
|
|
388
|
+
url: string;
|
|
389
|
+
};
|
|
390
|
+
remote: {
|
|
391
|
+
type: string;
|
|
392
|
+
url: string;
|
|
393
|
+
};
|
|
394
|
+
} | null;
|
|
395
|
+
currentRoundTripTime: number;
|
|
396
|
+
codecs: Record<string, string>;
|
|
397
|
+
receiveToCompositeMs: number;
|
|
398
|
+
bitrate: number;
|
|
399
|
+
}
|
|
388
400
|
/**
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
401
|
+
* Which orchestration transport was asked for the Cirrus endpoint:
|
|
402
|
+
* - `polling` — HTTP orchestration poll (`requestCirrusViaPolling`)
|
|
403
|
+
* - `socket` — orchestration control socket + `instanceReady`
|
|
404
|
+
* (`requestCirrusViaSocket`)
|
|
405
|
+
*
|
|
406
|
+
* Undefined when nothing was requested at all — a `ws://` URL that already points
|
|
407
|
+
* at Cirrus (`requestCirrusDirect`, dev/playwright). An adopted prefetched stream
|
|
408
|
+
* carries the kind the script actually used, so its state matches the native flow.
|
|
409
|
+
*
|
|
410
|
+
* Replaces the previous hostname sniffing (`/^wss:\/\/orchestration-websocket/`)
|
|
411
|
+
* in `selectTotalProgress` — the flow is now stated, not guessed.
|
|
412
|
+
*/
|
|
413
|
+
type CirrusRequestKind = 'polling' | 'socket';
|
|
414
|
+
interface AwsInstance {
|
|
415
|
+
/**
|
|
416
|
+
* WebSocket URL of the CIRRUS the player connects to. In the socket flow
|
|
417
|
+
* this is the stream machine's `originInstanceUri`, NOT the orchestration
|
|
418
|
+
* URL (the control socket is tracked separately by `SignallingService`).
|
|
419
|
+
*/
|
|
420
|
+
wsUrl: string | null;
|
|
421
|
+
/** See {@link CirrusRequestKind}. */
|
|
422
|
+
kind?: CirrusRequestKind;
|
|
423
|
+
pollingUrl: string | null;
|
|
424
|
+
instanceName: string | null;
|
|
425
|
+
/**
|
|
426
|
+
* Unique identifier for the streaming request.
|
|
427
|
+
* Coming with a new orchestration message "instanceReserved".
|
|
428
|
+
*/
|
|
429
|
+
streamRequestId?: string;
|
|
430
|
+
/**
|
|
431
|
+
* Estimated time of arrival (in seconds) for the instance to be ready.
|
|
432
|
+
* Coming with a new orchestration message "instanceReserved".
|
|
433
|
+
*/
|
|
434
|
+
eta?: number;
|
|
435
|
+
/**
|
|
436
|
+
* Progress of completion for the instance launch (0-1).
|
|
437
|
+
* Based on ETA time comes from the backend with new orchestration.
|
|
438
|
+
* If eta is not provided, this value will be undefined.
|
|
439
|
+
*/
|
|
440
|
+
progressComplete?: number;
|
|
441
|
+
message?: string;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Live connection parked on `window.__stream` by `stream-prefetch.js` for
|
|
446
|
+
* {@link StreamAdoptionService} to adopt. Handoff contract — keep both in sync.
|
|
393
447
|
*/
|
|
394
448
|
interface ParkedStream {
|
|
395
449
|
state: 'connecting' | 'ready' | 'failed' | 'adopted';
|
|
@@ -397,7 +451,7 @@ interface ParkedStream {
|
|
|
397
451
|
dc?: RTCDataChannel;
|
|
398
452
|
/** video(+audio) MediaStream from the `track` event */
|
|
399
453
|
stream?: MediaStream;
|
|
400
|
-
/**
|
|
454
|
+
/** Cirrus socket, handshake already done. */
|
|
401
455
|
ws?: WebSocket;
|
|
402
456
|
/** `data` from the streamer's `ssInfo` message — received before adoption */
|
|
403
457
|
ssData?: SignalingData;
|
|
@@ -405,13 +459,23 @@ interface ParkedStream {
|
|
|
405
459
|
machineId?: string;
|
|
406
460
|
/** orchestration endpoint the script polled to get `machineId` */
|
|
407
461
|
pollingUrl?: string;
|
|
462
|
+
/** Transport that produced it, so the adopted state matches the native flow. */
|
|
463
|
+
kind?: CirrusRequestKind;
|
|
464
|
+
/** Machine id: `originInstanceId` (socket) / `signallingServer` (polling). */
|
|
465
|
+
instanceName?: string | null;
|
|
466
|
+
/** Provisioning estimate from `instanceReserved`; socket flow only. */
|
|
467
|
+
eta?: number | null;
|
|
468
|
+
/**
|
|
469
|
+
* Control socket, handed over still open (the orchestration closes its own).
|
|
470
|
+
* Socket flow only — parked so the service can tear it down.
|
|
471
|
+
*/
|
|
472
|
+
controlWs?: WebSocket;
|
|
408
473
|
/** `peerConnectionOptions` the machine sent in its `config` message */
|
|
409
474
|
config?: RTCConfiguration;
|
|
410
475
|
/**
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* backend didn't issue one.
|
|
476
|
+
* Reused as-is, none generated: `clientId`/`viewId` from the configurator
|
|
477
|
+
* cache (same values the host app dispatches), `correlationId` is the
|
|
478
|
+
* configurator id, `streamRequestId` comes from the orchestration.
|
|
415
479
|
*/
|
|
416
480
|
ids: {
|
|
417
481
|
clientId: string;
|
|
@@ -428,16 +492,26 @@ interface ParkedStream {
|
|
|
428
492
|
/** Stops the script's watchdog and flips state to `adopted`. */
|
|
429
493
|
adopt: () => ParkedStream;
|
|
430
494
|
/**
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
* how {@link StreamAdoptionService} adopts event-driven (no polling) — the
|
|
434
|
-
* head-script must provide it.
|
|
495
|
+
* One-shot callback fired the instant the stream settles; on the next
|
|
496
|
+
* microtask if already settled. Required — adoption waits on it.
|
|
435
497
|
*/
|
|
436
498
|
onSettled: (cb: (state: ParkedStream['state']) => void) => void;
|
|
437
499
|
}
|
|
500
|
+
/** Marker the service writes when it finds the slot free and connects itself. */
|
|
501
|
+
interface AppOwnedStream {
|
|
502
|
+
owner: 'app';
|
|
503
|
+
}
|
|
504
|
+
/** True when the slot is the app's own marker rather than a prefetched stream. */
|
|
505
|
+
declare function isAppOwned(slot: ParkedStream | AppOwnedStream): slot is AppOwnedStream;
|
|
438
506
|
declare global {
|
|
439
507
|
interface Window {
|
|
440
|
-
|
|
508
|
+
/**
|
|
509
|
+
* The page's stream slot AND the lock: first writer owns the connection,
|
|
510
|
+
* whoever finds it taken stands down. Both sides read-then-write with no
|
|
511
|
+
* `await` between, so exactly one wins and a page never builds two
|
|
512
|
+
* connections. An older script writes no `owner` — still "taken".
|
|
513
|
+
*/
|
|
514
|
+
__stream?: ParkedStream | AppOwnedStream;
|
|
441
515
|
}
|
|
442
516
|
}
|
|
443
517
|
|
|
@@ -446,8 +520,6 @@ interface InputProps {
|
|
|
446
520
|
suppressBrowserKeys: boolean;
|
|
447
521
|
}
|
|
448
522
|
|
|
449
|
-
type Quality = 'lime' | 'red' | 'orange';
|
|
450
|
-
|
|
451
523
|
declare const OrchestrationMessageTypes: {
|
|
452
524
|
readonly error: "error";
|
|
453
525
|
readonly offer: "offer";
|
|
@@ -929,81 +1001,6 @@ type UnrealUiClasses = Partial<Record<UnrealUiPart, string | string[]>>;
|
|
|
929
1001
|
|
|
930
1002
|
type TelemetryType = 'commandSent' | 'commandStop';
|
|
931
1003
|
|
|
932
|
-
interface AggregatedStat {
|
|
933
|
-
quality: Quality;
|
|
934
|
-
aggregatedStats: InboundVideoStats;
|
|
935
|
-
}
|
|
936
|
-
interface InboundVideoStats extends RTCInboundRtpStreamStats {
|
|
937
|
-
VideoEncoderQP: number;
|
|
938
|
-
runTime: number;
|
|
939
|
-
pixelRatio: number;
|
|
940
|
-
bytesReceivedStart: number;
|
|
941
|
-
framesDecodedStart: number;
|
|
942
|
-
timestampStart: number;
|
|
943
|
-
videoCodec: string;
|
|
944
|
-
avgBitrate: number;
|
|
945
|
-
avgFrameRate: number;
|
|
946
|
-
selectedPair: {
|
|
947
|
-
local: {
|
|
948
|
-
type: string;
|
|
949
|
-
url: string;
|
|
950
|
-
};
|
|
951
|
-
remote: {
|
|
952
|
-
type: string;
|
|
953
|
-
url: string;
|
|
954
|
-
};
|
|
955
|
-
} | null;
|
|
956
|
-
currentRoundTripTime: number;
|
|
957
|
-
codecs: Record<string, string>;
|
|
958
|
-
receiveToCompositeMs: number;
|
|
959
|
-
bitrate: number;
|
|
960
|
-
}
|
|
961
|
-
/**
|
|
962
|
-
* Which orchestration transport was asked for the Cirrus endpoint:
|
|
963
|
-
* - `polling` — HTTP orchestration poll (`requestCirrusViaPolling`)
|
|
964
|
-
* - `socket` — orchestration control socket + `instanceReady`
|
|
965
|
-
* (`requestCirrusViaSocket`)
|
|
966
|
-
*
|
|
967
|
-
* Undefined when nothing was requested at all — a `ws://` URL that already points
|
|
968
|
-
* at Cirrus (`requestCirrusDirect`, dev/playwright) or an adopted prefetched
|
|
969
|
-
* stream. Those are not request kinds, they are the absence of a request.
|
|
970
|
-
*
|
|
971
|
-
* Replaces the previous hostname sniffing (`/^wss:\/\/orchestration-websocket/`)
|
|
972
|
-
* in `selectTotalProgress` — the flow is now stated, not guessed.
|
|
973
|
-
*/
|
|
974
|
-
type CirrusRequestKind = 'polling' | 'socket';
|
|
975
|
-
interface AwsInstance {
|
|
976
|
-
/**
|
|
977
|
-
* WebSocket URL of the CIRRUS the player connects to. In the socket flow
|
|
978
|
-
* this is the stream machine's `originInstanceUri`, NOT the orchestration
|
|
979
|
-
* URL (the control socket is tracked separately by `SignallingService`).
|
|
980
|
-
*/
|
|
981
|
-
wsUrl: string | null;
|
|
982
|
-
/** See {@link CirrusRequestKind}. */
|
|
983
|
-
kind?: CirrusRequestKind;
|
|
984
|
-
pollingUrl: string | null;
|
|
985
|
-
instanceName: string | null;
|
|
986
|
-
/**
|
|
987
|
-
* Unique identifier for the streaming request.
|
|
988
|
-
* Coming with a new orchestration message "instanceReserved".
|
|
989
|
-
*/
|
|
990
|
-
streamRequestId?: string;
|
|
991
|
-
/**
|
|
992
|
-
* Estimated time of arrival (in seconds) for the instance to be ready.
|
|
993
|
-
* Coming with a new orchestration message "instanceReserved".
|
|
994
|
-
*/
|
|
995
|
-
eta?: number;
|
|
996
|
-
/**
|
|
997
|
-
* Progress of completion for the instance launch (0-1).
|
|
998
|
-
* Based on ETA time comes from the backend with new orchestration.
|
|
999
|
-
* If eta is not provided, this value will be undefined.
|
|
1000
|
-
*/
|
|
1001
|
-
progressComplete?: number;
|
|
1002
|
-
message?: string;
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
declare const UNREAL_CONFIG: InjectionToken<UnrealInitialConfig>;
|
|
1006
|
-
|
|
1007
1004
|
declare const InputOptions: InputProps;
|
|
1008
1005
|
|
|
1009
1006
|
/** Disconnect reasons that should trigger remote connection cleanup. */
|
|
@@ -1543,7 +1540,7 @@ declare class SignallingService extends SubService {
|
|
|
1543
1540
|
* {@link OnOpenHandler} (which would re-`setCirrusConnected`). Reuses the
|
|
1544
1541
|
* head-script's `correlationId` so correlated messages line up.
|
|
1545
1542
|
*/
|
|
1546
|
-
adoptSocket(ws: WebSocket, correlationId: string): void;
|
|
1543
|
+
adoptSocket(ws: WebSocket, correlationId: string, controlWs?: WebSocket | null): void;
|
|
1547
1544
|
private showStatusMessage;
|
|
1548
1545
|
/**
|
|
1549
1546
|
* Abort/teardown: closes the Cirrus socket AND the control one, handed over or
|
|
@@ -1621,44 +1618,32 @@ declare class StreamAdoptionService {
|
|
|
1621
1618
|
private readonly signalling;
|
|
1622
1619
|
private readonly webRtc;
|
|
1623
1620
|
/**
|
|
1624
|
-
* Decide adoption, WAITING for an in-flight prefetch instead of racing it
|
|
1621
|
+
* Decide adoption, WAITING for an in-flight prefetch instead of racing it:
|
|
1622
|
+
* nothing parked → `false`; settled → adopt-if-ready; still `connecting` →
|
|
1623
|
+
* wait for `onSettled`. Emits exactly once.
|
|
1625
1624
|
*
|
|
1626
|
-
*
|
|
1627
|
-
* -
|
|
1628
|
-
*
|
|
1629
|
-
*
|
|
1625
|
+
* NO time budget: provisioning can take minutes, and a deadline would open a
|
|
1626
|
+
* second connection mid-flight. Bounded by liveness instead — the script
|
|
1627
|
+
* flips to `failed` on a dead socket — and by the caller's
|
|
1628
|
+
* `takeUntil(abortEstablishingConnection)`.
|
|
1630
1629
|
*
|
|
1631
|
-
*
|
|
1632
|
-
*
|
|
1633
|
-
*
|
|
1634
|
-
* second connection mid-provisioning — exactly the race/orphan this avoids.
|
|
1635
|
-
* The wait is bounded by liveness instead: the script flips the parked state
|
|
1636
|
-
* to `failed` on a dead socket (`ws.onclose`/`onerror`), and the caller wraps
|
|
1637
|
-
* this in `takeUntil(abortEstablishingConnection)`, so a route change / teardown
|
|
1638
|
-
* cancels it. Emits exactly once. Cancelling runs no adoption — which is what
|
|
1639
|
-
* stops a fast route-switch from starting overlapping connections.
|
|
1640
|
-
*
|
|
1641
|
-
* Wrapped in `defer` so the `tryAdopt()` side effect (which dispatches
|
|
1642
|
-
* `streamAdopted`) runs at SUBSCRIPTION time, not when the observable is
|
|
1643
|
-
* built. The effect prepends `startWith(setEstablishingConnection(true))` —
|
|
1644
|
-
* the single dispatch site for that boundary — which starts the telemetry
|
|
1645
|
-
* session and subscribes its phase catalog; that must happen BEFORE
|
|
1646
|
-
* `streamAdopted`, or `emitAdoptedConnection` fires into a session that does
|
|
1647
|
-
* not exist yet and the whole connection waterfall is lost. `defer`
|
|
1648
|
-
* guarantees that ordering.
|
|
1630
|
+
* `defer` so the `streamAdopted` dispatch happens at SUBSCRIPTION time: the
|
|
1631
|
+
* effect's `startWith(setEstablishingConnection(true))` must land first, or
|
|
1632
|
+
* the telemetry session does not exist yet and the waterfall is lost.
|
|
1649
1633
|
*/
|
|
1650
1634
|
awaitAndAdopt$(): Observable<boolean>;
|
|
1651
1635
|
/**
|
|
1652
|
-
* Event-driven wait
|
|
1653
|
-
*
|
|
1654
|
-
*
|
|
1655
|
-
* No timer/budget by design: an indefinitely `connecting` stream is valid
|
|
1656
|
-
* (DIRECT orchestration holds the socket open for minutes while it
|
|
1657
|
-
* provisions), and every terminal state — including a dead socket — flips to
|
|
1658
|
-
* `ready`/`failed` and fires the hook. Teardown (route change → unsubscribe)
|
|
1659
|
-
* sets `done`, so a late callback after cancel can't trigger a stray adoption.
|
|
1636
|
+
* Event-driven wait on the script's `onSettled`; emits exactly once. No timer
|
|
1637
|
+
* — see `awaitAndAdopt$`. Teardown sets `done`, so a late callback after
|
|
1638
|
+
* cancel can't trigger a stray adoption.
|
|
1660
1639
|
*/
|
|
1661
1640
|
private awaitSettled$;
|
|
1641
|
+
/** Every `false` here means we are about to connect ourselves — take the slot. */
|
|
1642
|
+
private tryAdoptOrTakeSlot;
|
|
1643
|
+
/** Mark the slot as the app's, replacing a spent prefetch object. */
|
|
1644
|
+
private takeSlot;
|
|
1645
|
+
/** Best-effort teardown of a parked stream we are about to abandon. */
|
|
1646
|
+
private closeParked;
|
|
1662
1647
|
/**
|
|
1663
1648
|
* @returns `true` if a ready parked stream was adopted (caller must NOT
|
|
1664
1649
|
* start a fresh connection), `false` otherwise.
|
|
@@ -3028,6 +3013,19 @@ declare const streamAdopted: _ngrx_store.ActionCreator<string, (props: {
|
|
|
3028
3013
|
* `connectionId` (see `selectCirrusConnectionId`).
|
|
3029
3014
|
*/
|
|
3030
3015
|
streamRequestId: string | null;
|
|
3016
|
+
/**
|
|
3017
|
+
* Which transport the prefetch used. Lets the adopted instance carry the
|
|
3018
|
+
* same `kind` the service's own flow would have set.
|
|
3019
|
+
*/
|
|
3020
|
+
kind: CirrusRequestKind | null;
|
|
3021
|
+
/**
|
|
3022
|
+
* The machine's own id (`originInstanceId` / `signallingServer`). `null`
|
|
3023
|
+
* from a script that predates this field — the reducer then falls back to a
|
|
3024
|
+
* placeholder label.
|
|
3025
|
+
*/
|
|
3026
|
+
instanceName: string | null;
|
|
3027
|
+
/** Provisioning estimate from `instanceReserved`; socket flow only. */
|
|
3028
|
+
eta: number | null;
|
|
3031
3029
|
/**
|
|
3032
3030
|
* `performance.now()` marks the prefetch script recorded while it
|
|
3033
3031
|
* established the connection (`prefetch_start` / `ws_open` / `dc_open` /
|
|
@@ -3049,6 +3047,19 @@ declare const streamAdopted: _ngrx_store.ActionCreator<string, (props: {
|
|
|
3049
3047
|
* `connectionId` (see `selectCirrusConnectionId`).
|
|
3050
3048
|
*/
|
|
3051
3049
|
streamRequestId: string | null;
|
|
3050
|
+
/**
|
|
3051
|
+
* Which transport the prefetch used. Lets the adopted instance carry the
|
|
3052
|
+
* same `kind` the service's own flow would have set.
|
|
3053
|
+
*/
|
|
3054
|
+
kind: CirrusRequestKind | null;
|
|
3055
|
+
/**
|
|
3056
|
+
* The machine's own id (`originInstanceId` / `signallingServer`). `null`
|
|
3057
|
+
* from a script that predates this field — the reducer then falls back to a
|
|
3058
|
+
* placeholder label.
|
|
3059
|
+
*/
|
|
3060
|
+
instanceName: string | null;
|
|
3061
|
+
/** Provisioning estimate from `instanceReserved`; socket flow only. */
|
|
3062
|
+
eta: number | null;
|
|
3052
3063
|
/**
|
|
3053
3064
|
* `performance.now()` marks the prefetch script recorded while it
|
|
3054
3065
|
* established the connection (`prefetch_start` / `ws_open` / `dc_open` /
|
|
@@ -3470,5 +3481,5 @@ declare const selectUserInactivityDetected: _ngrx_store.MemoizedSelector<Record<
|
|
|
3470
3481
|
/** Used by external consumers (e.g., metabox). Do not remove. */
|
|
3471
3482
|
declare const selectIsVideoPlayingAndDataChannelConnected: _ngrx_store.MemoizedSelector<Record<string, any>, boolean, (s1: boolean, s2: boolean) => boolean>;
|
|
3472
3483
|
|
|
3473
|
-
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, INSTANCE_READY_TIMEOUT, 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, 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,
|
|
3474
|
-
export type { AggregatedStat, AnySignalingMessage, AwsInstance, BootstrapInfo, CandidateMessage, CirrusRequestKind, CloseReason, CommandInProgress, ConfigMessage, ConnectionError, ControlSchemeValues, DisconnectReasonType, EmittedMilestone, EventId, FpsLowData, FreezeFrameMessage, InboundVideoStats, InputProps, InstanceReady, InstanceReserved, InterruptClientStream, ListStreamersMessage, MessageBase, MessageTypeValues, OrchestrationErrorMessage, OrchestrationMessageType, P2PEstablishedMessage, ParkedStream, PhaseEndedData, PhaseOverlay, PhaseParam, 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, UnrealAfkSlotContext, UnrealCallbackDescriptor, UnrealCallbackEventMap, UnrealCallbackJson, UnrealCallbackJsonWrapped,
|
|
3484
|
+
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, INSTANCE_READY_TIMEOUT, 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, 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, UnrealAfkSlotDirective, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealResumeSlotDirective, UnrealSceneComponent, UnrealStatusMessage, UnrealStatusSlotDirective, 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, isAppOwned, isMetaboxMode, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, provideUnrealState, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, resolveSkin, 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, startStream, streamAdopted, trackMixpanelEvent, trackTelemetryData, uint8ToBase64, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
|
|
3485
|
+
export type { AggregatedStat, AnySignalingMessage, AppOwnedStream, AwsInstance, BootstrapInfo, CandidateMessage, CirrusRequestKind, CloseReason, CommandInProgress, ConfigMessage, ConnectionError, ControlSchemeValues, DisconnectReasonType, EmittedMilestone, EventId, FpsLowData, FreezeFrameMessage, InboundVideoStats, InputProps, InstanceReady, InstanceReserved, InterruptClientStream, ListStreamersMessage, MessageBase, MessageTypeValues, OrchestrationErrorMessage, OrchestrationMessageType, P2PEstablishedMessage, ParkedStream, PhaseEndedData, PhaseOverlay, PhaseParam, 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, UnrealAfkSlotContext, UnrealCallbackDescriptor, UnrealCallbackEventMap, UnrealCallbackJson, UnrealCallbackJsonWrapped, UnrealResumeSlotContext, UnrealSlotTemplateContext, UnrealState, UnrealStatusSlotContext, UnrealUiClasses, UnrealUiPart, WSCloseCodesValues, WindowAggregate };
|