@3dsource/angular-unreal-module 0.0.146-dev.0 → 0.0.146-dev.1
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/js/stream-prefetch.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";(()=>{"use strict";const
|
|
1
|
+
"use strict";(()=>{"use strict";const f="metabox-configurator",m=/metabox-configurator\/(modular|basic)\/([^/?#]+)/i,g="3dsource:region-ping",w="assets/config.json",k="42e01f";function h(){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 I(o){return o.replace("http://","ws://").replace("https://","wss://")}const l="[stream-prefetch]",s=(...o)=>console.log(l,...o),c=(...o)=>console.warn(l,...o),u=(...o)=>console.error(l,...o);class y{constructor(){const e=document.currentScript||(()=>{const n=document.getElementsByTagName("script");return n[n.length-1]})(),t=e&&e.dataset||{};this.configUrl=t.configUrl||w,this.fallbackPollingUrl=t.fallbackUrl||"",this.skipRouteCheck=this.boolOr(t.skipRouteCheck,!1),this.config={watchdog_ms:this.numberOr(t.watchdogMs,15e3),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:h(),viewId:h(),correlationId:h()},this.context=null,this.environmentId=void 0,this.orchestrationType="",this.region="",this.requireEnvironmentId=!0,this.pc=null,this.dc=null,this.stream=null,this.ws=null,this.remoteDescriptionSet=!1,this.iceBuffer=[],this.watchdogId=null,this.wsTimeoutId=null,this.settled=!1}run(){if(typeof window=="undefined"||!("WebSocket"in window)||!("RTCPeerConnection"in window))return;if(this.skipRouteCheck){this.context={},this.requireEnvironmentId=!1,c("skipRouteCheck=true \u2014 ignoring URL gate, route, context and environmentId; using fallback polling"),this.publishParked("connecting"),this.connect().catch(t=>this.fail("exception",t));return}if(window.location.href.indexOf(f)===-1){s("skip \u2014 URL does not contain gate",`"${f}"`);return}const e=this.parseConfiguratorUrl();e?(this.context={id:e.contextId,data:{configuratorId:e.configuratorId}},this.requireEnvironmentId=!0,s("start",{contextId:e.contextId,configuratorId:e.configuratorId,ids:this.ids})):(this.context={},this.requireEnvironmentId=!1,c("URL is not a configurator route \u2014 prefetching with empty context {} and no required environmentId",window.location.pathname)),this.publishParked("connecting"),this.connect().catch(t=>this.fail("exception",t))}parseConfiguratorUrl(){const e=window.location.pathname.match(m);return e?{contextId:e[1].toLowerCase()==="modular"?"metaboxModularConfigurator":"metaboxBasicConfigurator",configuratorId:decodeURIComponent(e[2])}:null}connect(){return this.mark("prefetch_start"),this.region=this.readRegion()||"",s("region from cache:",this.region||"(none)"),(this.skipRouteCheck?Promise.resolve(null):this.fetchConfig()).then(t=>{let i;if(t)i=this.chooseEndpoint(t);else if(this.fallbackPollingUrl)c("no app config \u2014 falling back to polling",this.fallbackPollingUrl),i={type:"polling",url:this.fallbackPollingUrl,environmentId:""},this.requireEnvironmentId=!1;else{this.fail("no-config");return}if(!i){this.fail("no-endpoint");return}if(s("endpoint chosen",{type:i.type,url:i.url,environmentId:i.environmentId}),this.requireEnvironmentId&&!i.environmentId){this.fail("no-environment-id");return}this.orchestrationType=i.type,this.environmentId=i.environmentId||void 0;const n=this.adaptUrlToRegion(i.url);return(i.type==="polling"?this.pollOrchestration(n):Promise.resolve(n)).then(a=>{if(!a){this.fail("no-ws-url");return}return this.parked.machineId=a,s("opening socket",a),this.openSocket(a)})})}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.newSignalling,i=this.firstVersionUrl(t&&t.versions);if(i&&/^wss?:\/\//i.test(i))return{type:"direct",url:i,environmentId:t&&t.environmentId||""};const n=e.signalling,r=this.firstVersionUrl(n&&n.versions);return r?{type:"polling",url:r,environmentId:n&&n.environmentId||""}:null}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?I(`${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"),s("WS open \u2192 sending requestStream",{environmentId:this.environmentId,region:this.region,context:this.context}),this.sendRequestStream()},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"))},this.armWatchdog()}sendRequestStream(){this.send({type:"requestStream",correlationId:this.ids.correlationId,payload:{region:this.region,environmentId:this.environmentId,context:this.context}})}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(s("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,s("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]&&(s("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(()=>{s("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=()=>{s("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",s("READY \u2014 parked on window.__stream",this.parked.timings))}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.clearWatchdog(),this.state="adopted",this}},window.__stream=this.parked}armWatchdog(){this.clearWatchdog(),this.watchdogId=setTimeout(()=>{this.parked.state!=="adopted"&&(c(`not adopted within ${this.config.watchdog_ms}ms \u2014 tearing down`,{state:this.parked.state}),this.fail("watchdog"))},this.config.watchdog_ms)}clearWatchdog(){this.watchdogId&&(clearTimeout(this.watchdogId),this.watchdogId=null),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.clearWatchdog();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)))}}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
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 r of i)if(r.startsWith("a=fmtp:")&&r.toLowerCase().includes(`profile-level-id=${t}`)){const
|
|
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(
|
|
3
|
+
`),n=[];for(const r of i)if(r.startsWith("a=fmtp:")&&r.toLowerCase().includes(`profile-level-id=${t}`)){const a=r.split(":")[1]&&r.split(":")[1].split(" ")[0];a&&n.push(a)}return n.length===0?e:i.map(r=>{if(!r.startsWith("m=video"))return r;const a=r.split(" "),x=a.slice(0,3),p=a.slice(3),b=[...n.filter(d=>p.includes(d)),...p.filter(d=>!n.includes(d))];return[...x,...b].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(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}}fetchWithTimeout(e,t,i){const n=new AbortController;t.signal=n.signal;const r=setTimeout(()=>n.abort(),i);return fetch(e,t).finally(()=>clearTimeout(r))}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 y().run()})();
|
package/package.json
CHANGED
|
@@ -243,6 +243,16 @@ interface UnrealInitialConfig {
|
|
|
243
243
|
autoHighResolution?: boolean;
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
interface SignalingData {
|
|
247
|
+
instanceType: string;
|
|
248
|
+
branchInfo: string;
|
|
249
|
+
cirrusVersion: string;
|
|
250
|
+
streamerVersion: string;
|
|
251
|
+
streamPath: string;
|
|
252
|
+
streamApp: string;
|
|
253
|
+
connectionId: string;
|
|
254
|
+
}
|
|
255
|
+
|
|
246
256
|
/**
|
|
247
257
|
* Live WebRTC connection parked on `window.__stream` by the `stream-prefetch.js`
|
|
248
258
|
* head-script, ready for the Angular services to ADOPT instead of building a
|
|
@@ -257,6 +267,8 @@ interface ParkedStream {
|
|
|
257
267
|
stream?: MediaStream;
|
|
258
268
|
/** live signalling socket (handshake already done, `requestStream` sent) */
|
|
259
269
|
ws?: WebSocket;
|
|
270
|
+
/** `data` from the streamer's `ssInfo` message — received before adoption */
|
|
271
|
+
ssData?: SignalingData;
|
|
260
272
|
/** = resolved wsUrl of the winning machine */
|
|
261
273
|
machineId?: string;
|
|
262
274
|
/** `peerConnectionOptions` the machine sent in its `config` message */
|
|
@@ -422,16 +434,6 @@ interface InputProps {
|
|
|
422
434
|
|
|
423
435
|
type Quality = 'lime' | 'red' | 'orange';
|
|
424
436
|
|
|
425
|
-
interface SignalingData {
|
|
426
|
-
instanceType: string;
|
|
427
|
-
branchInfo: string;
|
|
428
|
-
cirrusVersion: string;
|
|
429
|
-
streamerVersion: string;
|
|
430
|
-
streamPath: string;
|
|
431
|
-
streamApp: string;
|
|
432
|
-
connectionId: string;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
437
|
declare const OrchestrationMessageTypes: {
|
|
436
438
|
readonly ping: "ping";
|
|
437
439
|
readonly error: "error";
|
|
@@ -1083,6 +1085,19 @@ declare class StreamAdoptionService {
|
|
|
1083
1085
|
private readonly store;
|
|
1084
1086
|
private readonly signalling;
|
|
1085
1087
|
private readonly webRtc;
|
|
1088
|
+
/**
|
|
1089
|
+
* Decide adoption, WAITING for an in-flight prefetch instead of racing it.
|
|
1090
|
+
*
|
|
1091
|
+
* - nothing parked → emit `false` at once (caller connects normally).
|
|
1092
|
+
* - already settled (`ready`/`failed`/`adopted`) → adopt-if-ready at once.
|
|
1093
|
+
* - still `connecting` → poll until it settles, then adopt-if-ready. Bounded
|
|
1094
|
+
* by `timeoutMs` so a stuck prefetch can't block the fallback forever.
|
|
1095
|
+
*
|
|
1096
|
+
* Emits exactly once. Cancel by unsubscribing (the effect does this on
|
|
1097
|
+
* teardown via `takeUntil`) — no adoption runs after cancellation, which is
|
|
1098
|
+
* what stops a fast route-switch from starting overlapping connections.
|
|
1099
|
+
*/
|
|
1100
|
+
awaitAndAdopt$(timeoutMs: number): Observable<boolean>;
|
|
1086
1101
|
/**
|
|
1087
1102
|
* @returns `true` if a ready parked stream was adopted (caller must NOT
|
|
1088
1103
|
* start a fresh connection), `false` otherwise.
|
|
@@ -2553,8 +2568,10 @@ declare const initSignalling: node_modules__ngrx_store_types_ngrx_store.ActionCr
|
|
|
2553
2568
|
*/
|
|
2554
2569
|
declare const streamAdopted: node_modules__ngrx_store_types_ngrx_store.ActionCreator<string, (props: {
|
|
2555
2570
|
machineId: string | null;
|
|
2571
|
+
ssData: SignalingData | null;
|
|
2556
2572
|
}) => {
|
|
2557
2573
|
machineId: string | null;
|
|
2574
|
+
ssData: SignalingData | null;
|
|
2558
2575
|
} & node_modules__ngrx_store_types_ngrx_store.Action<string>>;
|
|
2559
2576
|
declare const startStream: node_modules__ngrx_store_types_ngrx_store.ActionCreator<string, (props: {
|
|
2560
2577
|
config: Partial<StreamConfig>;
|
|
@@ -2770,7 +2787,9 @@ declare class UnrealEffects {
|
|
|
2770
2787
|
id: string;
|
|
2771
2788
|
now: number;
|
|
2772
2789
|
} & node_modules__ngrx_store_types_ngrx_store.Action<string>> & node_modules__ngrx_effects_types_ngrx_effects.CreateEffectMetadata;
|
|
2773
|
-
connectToSignaling$: node_modules_rxjs_dist_types.Observable<
|
|
2790
|
+
connectToSignaling$: node_modules_rxjs_dist_types.Observable<{
|
|
2791
|
+
value: boolean;
|
|
2792
|
+
} & node_modules__ngrx_store_types_ngrx_store.Action<string>> & node_modules__ngrx_effects_types_ngrx_effects.CreateEffectMetadata;
|
|
2774
2793
|
private canPeerReconnect;
|
|
2775
2794
|
/**
|
|
2776
2795
|
* Either schedules the next peer-reconnect attempt or gives up with the
|