@3dsource/angular-unreal-module 0.0.159 → 0.0.160
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/LICENSE +14 -14
- package/README.md +212 -212
- package/fesm2022/3dsource-angular-unreal-module.mjs +304 -227
- package/fesm2022/3dsource-angular-unreal-module.mjs.map +1 -1
- package/js/stream-prefetch.js +3 -3
- package/package.json +1 -1
- package/types/3dsource-angular-unreal-module.d.ts +39 -0
package/js/stream-prefetch.js
CHANGED
|
@@ -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]",
|
|
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]",n=(...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)){n("skip \u2014 not a metabox-configurator/{modular|basic} route");return}n("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()||"",n("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)){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(i.type==="polling"?this.pollOrchestration(s):Promise.resolve(s)).then(a=>{if(!a){this.fail("no-ws-url");return}return this.parked.machineId=a,n("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};const s=e.signalling,r=this.firstVersionUrl(s&&s.versions);return r?{type:"polling",url:r}: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"),n("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(n("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,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(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(()=>{n("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=()=>{n("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",n("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
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
|
|
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
|
|
3
|
+
`),s=[];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&&s.push(a)}return s.length===0?e:i.map(r=>{if(!r.startsWith("m=video"))return r;const a=r.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 r=setTimeout(()=>s.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 b().run()})();
|
package/package.json
CHANGED
|
@@ -1687,7 +1687,23 @@ declare class VideoService extends SubService {
|
|
|
1687
1687
|
videoStats$: rxjs.Observable<AggregatedStat>;
|
|
1688
1688
|
private destroyAbort;
|
|
1689
1689
|
constructor();
|
|
1690
|
+
/**
|
|
1691
|
+
* Sets (or replaces) the DOM target for the streaming `<video>`.
|
|
1692
|
+
*
|
|
1693
|
+
* Attaching is idempotent and order-independent on purpose: `create()` is
|
|
1694
|
+
* driven by the network (`config`, or a synchronous prefetch adoption) while
|
|
1695
|
+
* the container comes from the scene component's render pass, so either side
|
|
1696
|
+
* can win the race. If `create()` got there first the element is detached —
|
|
1697
|
+
* adopt it here instead of leaving it orphaned (which showed up as a white
|
|
1698
|
+
* scene with an empty `#player` and no `videoStreamInitialized`).
|
|
1699
|
+
*/
|
|
1690
1700
|
setContainer(container?: HTMLDivElement | null): void;
|
|
1701
|
+
/**
|
|
1702
|
+
* Moves `this.video` under the current container unless it is already there.
|
|
1703
|
+
* `parentElement` (not `isConnected`) is the guard: it also covers a swap to a
|
|
1704
|
+
* different container while the element sits in a still-live old one.
|
|
1705
|
+
*/
|
|
1706
|
+
private attachVideoToContainer;
|
|
1691
1707
|
setLatencyTimings(latencyTimings: string): void;
|
|
1692
1708
|
setEncoder(data: ArrayBuffer): void;
|
|
1693
1709
|
create(): void;
|
|
@@ -1695,8 +1711,31 @@ declare class VideoService extends SubService {
|
|
|
1695
1711
|
attachAudioStream(stream: MediaStream): void;
|
|
1696
1712
|
play(): void;
|
|
1697
1713
|
safePlay(video: HTMLVideoElement | null): Promise<void>;
|
|
1714
|
+
/**
|
|
1715
|
+
* Retries playback on the next real user gesture — the only thing that lifts
|
|
1716
|
+
* a browser autoplay block. Shared by `safePlay()` and `playAudio()` so both
|
|
1717
|
+
* media elements use one arming/cleanup path.
|
|
1718
|
+
*
|
|
1719
|
+
* Listeners are `once` + `capture` and bound to `destroyAbort.signal`, so a
|
|
1720
|
+
* `destroy()` in this session drops them instead of leaking into the next one.
|
|
1721
|
+
*/
|
|
1722
|
+
private armGestureRetry;
|
|
1698
1723
|
private getStats;
|
|
1699
1724
|
private generateAggregatedStatsFunction;
|
|
1725
|
+
/**
|
|
1726
|
+
* Folds one `inbound-rtp` video report into the accumulator: copies the raw
|
|
1727
|
+
* report, pins the per-session baselines on the first pass, then derives the
|
|
1728
|
+
* deltas relative to the previous pass.
|
|
1729
|
+
*/
|
|
1730
|
+
private applyInboundVideoReport;
|
|
1731
|
+
/**
|
|
1732
|
+
* Resolves the transport's selected candidate pair into the local/remote
|
|
1733
|
+
* descriptors shown in the stats overlay. Silently skips when the pair (or
|
|
1734
|
+
* either of its candidates) is missing from this report.
|
|
1735
|
+
*/
|
|
1736
|
+
private applySelectedPair;
|
|
1737
|
+
/** Per-session values that do not come from a single report. */
|
|
1738
|
+
private applySessionTotals;
|
|
1700
1739
|
private onAggregatedStats;
|
|
1701
1740
|
private destroy;
|
|
1702
1741
|
private changeStatusMainVideoOnScene;
|