Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- assets/openarm-BF3lx1-l.js +14 -0
- assets/openarm-settings-Cing9rdF.js +141 -0
- assets/settings-CojHZpo_.js +1 -0
- openarm.html +2 -2
- settings.html +2 -2
assets/openarm-BF3lx1-l.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import"./modulepreload-polyfill-B5Qt9EMX.js";import{l as xs,f as Ht,i as Cs}from"./openarm-settings-Cing9rdF.js";import{c as $e,f as ie,B as $s}from"./connect-C3lO3qk6.js";import{W as Es,S as _s,A as Ms,D as pt,P as Fs,O as ks,G as As,a as Ds,B as Yt,b as Ze,c as Ts,d as Is,e as gt,M as yt,f as Ss,g as Ls,U as Rs,h as Ps,i as Us,C as Os,L as Bs,j as zs,k as qs,l as Vs,m as Hs,n as Ys,o as Be,V as ze,Q as at,E as Ws}from"./URDFLoader-BpNv9rVq.js";let te,Fe,Wt;const vt=4e3,Ns=5;function Gs(t,e,s){te=t,Fe=e,Wt=s}function b(t,e="info",{toast:s=!0,html:i}={}){const o=document.createElement("div");if(o.className=`log-entry log-${e}`,i?o.innerHTML=`[${new Date().toLocaleTimeString()}] ${i}`:o.textContent=`[${new Date().toLocaleTimeString()}] ${t}`,te.appendChild(o),te.children.length>200&&te.removeChild(te.firstChild),te.scrollTop=te.scrollHeight,!s)return;const n=document.createElement("div");for(n.className=`toast toast-${e}`,n.style.setProperty("--toast-duration",`${vt}ms`),n.textContent=t,Fe.prepend(n);Fe.children.length>Ns;)Fe.lastChild.remove();setTimeout(()=>n.remove(),vt+400)}function Js(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} K`:`${(t/(1024*1024)).toFixed(1)} M`}function Y(t){Wt.textContent=t}const et=[{name:"J1",desc:"Shoulder pan",canId:17,color:16739125},{name:"J2",desc:"Shoulder lift",canId:18,color:16747586},{name:"J3",desc:"Shoulder rot",canId:19,color:16755021},{name:"J4",desc:"Elbow flex",canId:20,color:16765286},{name:"J5",desc:"Wrist roll",canId:21,color:448160},{name:"J6",desc:"Wrist pitch",canId:22,color:1149618},{name:"J7",desc:"Wrist rot",canId:23,color:473932},{name:"Grip",desc:"Gripper",canId:24,color:8599788}],js=["L_","R_"];function Nt(t){const e=t&15;return e>=1&&e<=8?e-1:-1}function Ks(){return new Array(8).fill(null).map(()=>({angle:0,targetAngle:0,velocity:0,torque:0,tempMos:0,tempRotor:0,updated:!1}))}const pe=72,Qs=2147483648,Xs=536870911,Zs=2047;function en(t){if(t.length<pe)return null;const s=new DataView(t.buffer,t.byteOffset,pe).getUint32(0,!0),i=t[4],o=t[5],r=(s&Qs)!==0?s&Xs:s&Zs,a=t.slice(8,8+Math.min(i,64));return{flags:o,canId:r,dataLen:i,data:a}}function Gt(t){const e=[];let s=0;for(;s+pe<=t.length;){const i=t.subarray(s,s+pe);e.push(en(i)),s+=pe}return e}function Jt(t){if(t.length<8)return null;const e=t[1]<<8|t[2],s=t[3]<<4|t[4]>>4,i=(t[4]&15)<<8|t[5],o=12.5,n=e/65535*(2*o)-o,r=45,a=s/4095*(2*r)-r,c=18,l=i/4095*(2*c)-c,f=t[6],u=t[7];return{qRad:n,vel:a,tau:l,tempMos:f,tempRotor:u}}async function tn(){const t={};t.userAgent=navigator.userAgent,t.isSafari=/Safari/.test(navigator.userAgent)&&!/Chrome/.test(navigator.userAgent),t.hasWebTransport=typeof WebTransport<"u",t.hasMediaSource=typeof MediaSource<"u",t.hasManagedMediaSource=typeof ManagedMediaSource<"u",t.hasVideoDecoder=typeof VideoDecoder<"u",t.hasEncodedVideoChunk=typeof EncodedVideoChunk<"u",t.hasOffscreenCanvas=typeof OffscreenCanvas<"u";const e=window.MediaSource||window.ManagedMediaSource;if(e){t.mse={};for(const s of["av01.0.05M.08","av01.0.05M.10","avc1.640028","avc1.42E01E","hev1.1.6.L93.B0"]){const i=`video/mp4; codecs="${s}"`;t.mse[s]=e.isTypeSupported(i)}}if(t.hasVideoDecoder&&typeof VideoDecoder.isConfigSupported=="function"){t.videoDecoder={};for(const s of["av01.0.05M.08","av01.0.05M.10","avc1.640028","avc1.42E01E"])try{const i=await VideoDecoder.isConfigSupported({codec:s});t.videoDecoder[s]=i.supported}catch(i){t.videoDecoder[s]=`error: ${i.message}`}}return console.table(t.mse),console.table(t.videoDecoder),console.log("Full diagnostics:",t),t}typeof window<"u"&&(window.xoqDiag=tn,window._xoqCapturedFrame=null,window._xoqCapturedAv1C=null,window.xoqTestDecode=async(t,e)=>{const s=t.replace(/\s/g,""),i=new Uint8Array(s.length/2);for(let o=0;o<i.length;o++)i[o]=parseInt(s.substr(o*2,2),16);return bt(i,e,`test(${i.length}b)`)},window.xoqRetry=async()=>{const t=window._xoqCapturedFrame,e=window._xoqCapturedAv1C;if(!t)return console.log("No captured frame yet — connect first"),"no frame";const s=Kt(e?e.slice(4):null),i=s?s.codec:"av01.0.05M.10";let o=i;if(e&&e.length>=4){const g=e[1]>>5&7,y=e[1]&31,w=e[2]>>7&1,x=e[2]>>6&1,E=e[2]>>5&1?12:x?10:8;o=`av01.${g}.${String(y).padStart(2,"0")}${w?"H":"M"}.${String(E).padStart(2,"0")}`}console.log(`Captured: ${t.length}b, av1C: ${e?e.length+"b":"none"}, correct=${i}, orig=${o}, dims=${s?s.width+"x"+s.height:"?"}`);const n=ke(t),r=e&&e.length>4?e.slice(4):null,a=Ae(t),c=e?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):null,l=e?e.slice():null;l&&s&&(l[1]=s.profile<<5|s.level&31);const f=l?l.buffer.slice(l.byteOffset,l.byteOffset+l.byteLength):null,u=s?{codedWidth:s.width,codedHeight:s.height}:{};let h=null;r&&(h=new Uint8Array(r.length+n.length),h.set(r,0),h.set(n,r.length));const v=[["TD-stripped",a]];h&&v.push(["SeqHdr+frame",h]),v.push(["raw",t]);const p=[[i,"correct"]];o!==i&&p.push([o,"orig"]);const m=[["fixedDesc",f]];c&&m.push(["origDesc",c]),m.push(["noDesc",null]);const d=["prefer-software","no-preference"];for(const[g,y]of v)for(const[w,x]of p)for(const[$,E]of m)for(const M of d){const A=`${g}, ${x}, ${$}, ${M}`,C=await bt(y,w,A,E,u,M);if(C.startsWith("OK"))return console.log(`
|
| 2 |
+
=== SUCCESS: ${A} ===`),C}return console.log(`
|
| 3 |
+
=== ALL STRATEGIES FAILED ===`),"all failed"});async function bt(t,e,s,i,o,n){const r={codec:e};return i&&(r.description=i),o&&o.codedWidth&&(r.codedWidth=o.codedWidth,r.codedHeight=o.codedHeight),n&&(r.hardwareAcceleration=n),console.log(`[${s}] data=${t.length}b`),new Promise(a=>{const c=setTimeout(()=>{try{l.close()}catch{}const f="TIMEOUT";console.log(`[${s}] ${f}`),a(f)},5e3),l=new VideoDecoder({output:f=>{clearTimeout(c);const u=`OK: ${f.displayWidth}x${f.displayHeight} fmt=${f.format}`;console.log(`[${s}] ${u}`),f.close();try{l.close()}catch{}a(u)},error:f=>{clearTimeout(c);const u=`ERR: ${f.message}`;console.log(`[${s}] ${u}`);try{l.close()}catch{}a(u)}});try{l.configure(r)}catch(f){clearTimeout(c);const u=`CONFIGURE_ERR: ${f.message}`;console.log(`[${s}] ${u}`),a(u);return}l.decode(new EncodedVideoChunk({type:"key",timestamp:0,data:t})),l.flush().catch(()=>{})})}function Te(t){const e=new Uint8Array(t),s=i=>i.toString(16).padStart(2,"0").toUpperCase();for(let i=0;i<e.length-11;i++){if(e[i+4]===97&&e[i+5]===118&&e[i+6]===99&&e[i+7]===67){const o=i+8;if(o+4<=e.length)return`avc1.${s(e[o+1])}${s(e[o+2])}${s(e[o+3])}`}if(e[i+4]===97&&e[i+5]===118&&e[i+6]===49&&e[i+7]===67){const o=i+8;if(o+4<=e.length){const n=e[o+1]>>5&7,r=e[o+1]&31,a=e[o+2]>>7&1,c=e[o+2]>>6&1,l=e[o+2]>>5&1,f=c?l?12:10:8,u=String(r).padStart(2,"0");return`av01.${n}.${u}${a?"H":"M"}.${String(f).padStart(2,"0")}`}}}return null}function jt(t){return t.length>=8&&t[4]===102&&t[5]===116&&t[6]===121&&t[7]===112}function tt(t,e){const s=[e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2),e.charCodeAt(3)];let i=0;for(;i<t.length-8;){const o=t[i]<<24|t[i+1]<<16|t[i+2]<<8|t[i+3];if(t[i+4]===s[0]&&t[i+5]===s[1]&&t[i+6]===s[2]&&t[i+7]===s[3])return i;if(o<8)break;i+=o}return-1}function Je(t){const e=tt(t,"mdat");if(e<0)return null;const s=t[e]<<24|t[e+1]<<16|t[e+2]<<8|t[e+3];return t.subarray(e+8,e+s)}function sn(t){for(let e=0;e<t.length-11;e++)if(t[e+4]===97&&t[e+5]===118&&t[e+6]===49&&t[e+7]===67){const s=t[e]<<24|t[e+1]<<16|t[e+2]<<8|t[e+3];return t.slice(e+8,e+s)}return null}const wt=2,nn=1;function qe(t,e){let s=0,i=0;for(let o=0;o<8&&e+o<t.length;o++){const n=t[e+o];if(s|=(n&127)<<o*7,i++,!(n&128))break}return[s,i]}function on(t){let e=0;for(;e<t.length;){const s=t[e];if((s>>3&15)===nn)return!0;const o=s>>2&1,n=s>>1&1;let r=1+(o?1:0);if(!n)break;const[a,c]=qe(t,e+r);r+=c,e+=r+a}return!1}function Kt(t){const e=t[0]>>2&1,s=t[0]>>1&1;let i=1+(e?1:0);if(s){for(let r=0;i+r<t.length;r++)if(!(t[i+r]&128)){i+=r+1;break}}let o=i*8;const n=r=>{let a=0;for(let c=0;c<r;c++)a=a<<1|t[o>>3]>>7-(o&7)&1,o++;return a};try{const r=n(3);n(1);const a=n(1);let c,l=0;if(a)c=n(5);else{if(n(1))return null;const C=n(1),_=n(5);n(12),c=n(5),c>7&&(l=n(1)),C&&n(1)&&n(4);for(let F=1;F<=_;F++)n(12),n(5)>7&&n(1),C&&n(1)&&n(4)}const f=n(4)+1,u=n(4)+1,h=n(f)+1,v=n(u)+1;if(!a&&n(1)&&n(7),n(3),!a){n(4);const A=n(1);A&&n(2),(n(1)?2:n(1))>0&&(n(1)||n(1)),A&&n(3)}n(3);const p=n(1);let m=0;r===2&&p&&(m=n(1));const d=m?12:p?10:8;let g=-1,y=-1;const w=r!==1?n(1):0,x=n(1);let $=2;if(x&&(n(8),n(8),$=n(8)),!w&&$!==0){n(1);let A,C;r===0?(A=1,C=1):r===1?(A=0,C=0):d===12?(A=n(1),C=A?n(1):0):(A=1,C=0),A===1&&C===1&&(y=o,g=n(2))}const E=l?"H":"M";return{codec:`av01.${r}.${String(c).padStart(2,"0")}${E}.${String(d).padStart(2,"0")}`,profile:r,level:c,tier:l,bitDepth:d,width:h,height:v,chromaSamplePos:g,chromaSampleBitPos:y}}catch{return null}}function ke(t){const e=[];let s=0;for(;s<t.length;){const r=t[s],a=r>>3&15,c=r>>2&1,l=r>>1&1;let f=1+(c?1:0);if(!l){a>=3&&a<=6&&e.push(t.subarray(s));break}const[u,h]=qe(t,s+f);f+=h;const v=f+u,p=Math.min(s+v,t.length);a>=3&&a<=6&&e.push(t.subarray(s,p)),s=p}if(e.length===0)return new Uint8Array(0);let i=0;for(const r of e)i+=r.length;const o=new Uint8Array(i);let n=0;for(const r of e)o.set(r,n),n+=r.length;return o}function Ae(t){const e=[];let s=0,i=!1;for(;s<t.length;){const a=t[s],c=a>>3&15,l=a>>2&1,f=a>>1&1;let u=1+(l?1:0);if(!f){c!==wt?e.push(t.subarray(s)):i=!0;break}const[h,v]=qe(t,s+u);u+=v;const p=u+h,m=Math.min(s+p,t.length);c!==wt?e.push(t.subarray(s,m)):i=!0,s=m}if(!i)return t;let o=0;for(const a of e)o+=a.length;const n=new Uint8Array(o);let r=0;for(const a of e)n.set(a,r),r+=a.length;return n}function rn(t,e){if(t.length<8)return t;const s=t[0]|t[1]<<8|t[2]<<16|t[3]<<24,o=((t[4]|t[5]<<8|t[6]<<16|t[7]<<24)>>>0)*4294967296+(s>>>0);return o>17e11&&o<Date.now()+6e4&&(e.ms=Date.now()-o,e.lastUpdate=Date.now(),e.sum+=e.ms,e.samples++),t.subarray(8)}const st=typeof VideoDecoder<"u",nt=window.MediaSource||window.ManagedMediaSource,xt=!!nt;class Ie{constructor(){this.decoder=null,this.configured=!1,this.configuredCodec=null,this.latestY=null,this.is10bit=!1,this.width=0,this.height=0,this.frameCount=0,this.copyBuf=null,this.disabled=!st&&!xt,this._seqHdrObu=null,this._useMse=!1,this._msePlayer=null,this._mseVideo=null,this._mseCanvas=null,this._mseCtx=null,this._mseRafId=null,this._initSegment=null,this.disabled&&!Ie._warned&&(Ie._warned=!0,b("Depth not available (no WebCodecs or MSE)","info"))}onData(e){if(this.disabled)return;if(this._autoConfiguring){this._pendingData||(this._pendingData=[]),this._pendingData.push(new Uint8Array(e));return}const s=new Uint8Array(e);if(this._useMse){this._msePlayer&&this._msePlayer.onData(s);return}if(jt(s)){if(this._initSegment=s.slice(),!this.configured){const o=sn(s),n=Te(s);o&&(this._savedAv1c=o),n&&(this._savedCodec=n);const r=tt(s,"moof"),a=r>=0?s.subarray(r):null;if(o&&n&&a){this._startAutoConfig(n,o,a);return}return}if(this.configured){const o=tt(s,"moof");o>=0&&this.decodeSample(s.subarray(o))}}else!this.configured&&this._savedAv1c&&this._savedCodec?this._startAutoConfig(this._savedCodec,this._savedAv1c,s):this.configured&&this.decodeSample(s)}_startAutoConfig(e,s,i){if(typeof window<"u"){const o=Je(i);o&&(window._xoqCapturedFrame=o.slice()),window._xoqCapturedAv1C=s.slice()}this._autoConfiguring=!0,this._autoConfig(e,s,i).catch(o=>{b(`Depth auto-config error: ${o.message}`,"error"),this._switchToMse(e)}).finally(()=>{if(this._autoConfiguring=!1,this._pendingData){const o=this._pendingData;this._pendingData=null;for(const n of o)this.onData(n)}})}_testDecode(e,s){return new Promise(i=>{let o=!1;const n=(c,l)=>{o||(o=!0,clearTimeout(r),i([c,l]))},r=setTimeout(()=>{try{a.close()}catch{}n(!1,"timeout")},3e3);let a;try{a=new VideoDecoder({output:c=>{const l=c.format;c.close();try{a.close()}catch{}n(!0,`fmt=${l} ${c.displayWidth}x${c.displayHeight}`)},error:c=>{try{a.close()}catch{}n(!1,c.message)}}),a.configure(e),a.decode(new EncodedVideoChunk({type:"key",timestamp:0,data:s})),a.flush().catch(()=>{})}catch(c){n(!1,`threw: ${c.message}`)}})}async _autoConfig(e,s,i){if(!st){this._switchToMse(e);return}const o=n=>{var r,a;(a=(r=n.reason)==null?void 0:r.message)!=null&&a.includes("ReadableStreamDefaultController")&&n.preventDefault()};typeof window<"u"&&window.addEventListener("unhandledrejection",o);try{await this._autoConfigInner(e,s,i)}finally{typeof window<"u"&&window.removeEventListener("unhandledrejection",o)}}async _autoConfigInner(e,s,i){s.length>4&&(this._seqHdrObu=s.slice(4));const o=this._seqHdrObu?Kt(this._seqHdrObu):null,n=o?o.codec:e;o&&o.codec!==e&&b(`Depth: av1C says ${e}, SeqHdr says ${o.codec}`,"data",{toast:!1});const r=s.slice();if(o&&r.length>=4){r[1]=o.profile<<5|o.level&31;const p=o.bitDepth>8?1:0,m=o.bitDepth===12?1:0;r[2]=(o.tier&1)<<7|(p&1)<<6|(m&1)<<5|r[2]&31}const a=r.buffer.slice(r.byteOffset,r.byteOffset+r.byteLength),c=Je(i);if(!c||!c.length){this._switchToMse(e);return}const l=ke(c),f=Ae(c);let u;this._seqHdrObu?(u=new Uint8Array(this._seqHdrObu.length+l.length),u.set(this._seqHdrObu,0),u.set(l,this._seqHdrObu.length)):u=f;const h=o?{codedWidth:o.width,codedHeight:o.height}:{},v=[];v.push([{codec:n,description:a,hardwareAcceleration:"prefer-software"},u,this._seqHdrObu,"desc+seqhdr"]),v.push([{codec:n,description:a,hardwareAcceleration:"prefer-software",...h},u,this._seqHdrObu,"desc+seqhdr+dims"]),v.push([{codec:n,hardwareAcceleration:"no-preference",...h},f,null,"nodesc+inline"]),v.push([{codec:n,description:a,hardwareAcceleration:"no-preference"},u,this._seqHdrObu,"nopref+desc+seqhdr"]);for(let p=0;p<v.length;p++){const[m,d,g,y]=v[p],w=`${m.codec} hw=${m.hardwareAcceleration} ${y}`,[x,$]=await this._testDecode(m,d);if(x){if(b(`Depth: config #${p} works (${w}) ${$}`,"success"),this.decoder)try{this.decoder.close()}catch{}this.decoder=new VideoDecoder({output:E=>this.processFrame(E).catch(M=>console.error("Depth frame error:",M)),error:E=>{b(`Depth VideoDecoder error: ${E.message} — switching to MSE`,"info"),this._switchToMse(this.configuredCodec)}}),this.decoder.configure(m),this.configured=!0,this.configuredCodec=m.codec,g?this._seqHdrObu=g:y.includes("frameonly")?(this._seqHdrObu=null,this._useFrameOnly=!0):y.includes("raw")?(this._seqHdrObu=null,this._useRawMdat=!0):y.includes("inline")&&(this._seqHdrObu=null,this._useTdStrip=!0),b(`Depth WebCodecs: ${m.codec} ${o?o.width+"x"+o.height:""} (${y})`,"data",{toast:!1}),this.decodeSample(i);return}b(`Depth: #${p} FAIL ${w} — ${$}`,"data",{toast:!1})}b(`Depth: all ${v.length} WebCodecs configs failed — using MSE fallback`,"info"),this._switchToMse(e)}_switchToMse(e){if(this.decoder)try{this.decoder.close()}catch{}if(this.decoder=null,this.configured=!1,!xt){this.disabled=!0,b("Depth: MSE not available either","error");return}this._useMse=!0,this.configured=!0,this.configuredCodec=e,this._mseVideo=document.createElement("video"),this._mseVideo.muted=!0,this._mseVideo.playsInline=!0,this._mseVideo.style.cssText="position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;",document.body.appendChild(this._mseVideo),this._msePlayer=new X(this._mseVideo,"Depth MSE"),this._initSegment&&this._msePlayer.onData(this._initSegment);const s=()=>{this._useMse&&("requestVideoFrameCallback"in this._mseVideo?this._mseVideo.requestVideoFrameCallback(()=>i()):this._mseRafId=requestAnimationFrame(()=>i()))},i=async()=>{if(!this._useMse||this._mseExtracting){s();return}const o=this._mseVideo;if(!o||!o.videoWidth||o.readyState<2){s();return}if(typeof VideoFrame<"u"&&this._mseCanvasOnly!==!0)try{this._mseExtracting=!0;const f=new VideoFrame(o,{timestamp:o.currentTime*1e6});if(!this._mseFmtLogged){const u=f.format,h=u&&(u.includes("10")||u.includes("12"));b(`Depth MSE: VideoFrame fmt=${u} ${f.displayWidth}x${f.displayHeight}${h?" — true 10-bit!":""}`,"data"),this._mseFmtLogged=!0}await this.processFrame(f),this._mseExtracting=!1,s();return}catch(f){this._mseExtracting=!1,this._mseCanvasOnly=!0,b(`Depth MSE: VideoFrame from <video> failed (${f.message}), using canvas fallback`,"data",{toast:!1})}const n=o.videoWidth,r=o.videoHeight;(!this._mseCanvas||this._mseCanvas.width!==n||this._mseCanvas.height!==r)&&(this._mseCanvas=document.createElement("canvas"),this._mseCanvas.width=n,this._mseCanvas.height=r,this._mseCtx=this._mseCanvas.getContext("2d",{willReadFrequently:!0})),this._mseCtx.drawImage(o,0,0,n,r);const a=this._mseCtx.getImageData(0,0,n,r).data,c=n*r;if(this.configuredCodec&&this.configuredCodec.endsWith(".10")){(!this.latestY||this.latestY.length!==c)&&(this.latestY=new Uint16Array(c));for(let f=0;f<c;f++){const u=a[f*4];this.latestY[f]=u<2?0:Math.min(1023,Math.round(u*3.435+64))}this.is10bit=!0}else{(!this.latestY||this.latestY.length!==c)&&(this.latestY=new Uint8Array(c));for(let f=0;f<c;f++)this.latestY[f]=a[f*4];this.is10bit=!1}this.width=n,this.height=r,s()};s(),b("Depth: MSE fallback active","success")}decodeSample(e){const s=Je(e);if(!s||!s.length||!this.decoder||this.decoder.state!=="configured")return;const i=this.frameCount*(1e6/30),o=on(s);o&&typeof window<"u"&&!window._xoqCapturedFrame&&(window._xoqCapturedFrame=s.slice());let n;if(this._useRawMdat)n=s;else if(this._useFrameOnly)n=ke(s);else if(this._useTdStrip)n=Ae(s);else if(this._seqHdrObu){const r=ke(s);n=new Uint8Array(this._seqHdrObu.length+r.length),n.set(this._seqHdrObu,0),n.set(r,this._seqHdrObu.length)}else n=Ae(s);if(this.frameCount<2){const r=Array.from(n.subarray(0,Math.min(32,n.length)),l=>l.toString(16).padStart(2,"0")).join(" "),a=[];let c=0;for(;c<n.length;){const l=n[c],f=l>>3&15,u=l>>2&1,h=l>>1&1;let v=1+(u?1:0);if(!h){a.push(`t${f}`);break}const[p,m]=qe(n,c+v);v+=m,a.push(`t${f}(${p})`),c+=v+p}b(`Depth chunk#${this.frameCount}: ${o?"KEY":"delta"} ${n.length}b OBUs=[${a.join(",")}] hex=${r}`,"data",{toast:!1})}try{this.decoder.decode(new EncodedVideoChunk({type:o?"key":"delta",timestamp:i,data:n}))}catch(r){b(`Depth decode() threw: ${r.message}`,"error")}this.frameCount++}async processFrame(e){try{const s=e.displayWidth,i=e.displayHeight,o=s*i,n=e.format;let r=!1;if(n){const a=e.allocationSize();(!this.copyBuf||this.copyBuf.byteLength<a)&&(this.copyBuf=new ArrayBuffer(a));const c=await e.copyTo(this.copyBuf),l=c[0].offset,f=c[0].stride;this._rawDiag={fmt:n,w:s,h:i,totalSize:a,numLayouts:c.length,layouts:c.map(h=>({offset:h.offset,stride:h.stride})),codedWidth:e.codedWidth,codedHeight:e.codedHeight,displayWidth:e.displayWidth,displayHeight:e.displayHeight};const u=n.includes("BGR")||n.includes("RGB");if(!this.fmtLogged){const h=u?Math.floor(f/4):f;b(`Depth frame: fmt=${n}, ${s}x${i}, stride=${f}, pxPerRow=${h}, packed=${u}`,"data"),this.fmtLogged=!0}if(u){const h=new Uint8Array(this.copyBuf),v=4;if(!this._halfWidthDetected){const d=l+(i-1)*f;let g=0,y=0;for(let w=Math.floor(s/2);w<s;w+=10)h[d+w*v+1]===0&&g++,y++;this._isHalfWidth=y>0&&g/y>.8,this._halfWidthDetected=!0,this._isHalfWidth&&b(`Depth BGRX: half-width bug detected, scaling ${s/2} → ${s}`,"info")}const p=this._isHalfWidth?Math.floor(s/2):s;if(this.configuredCodec&&this.configuredCodec.endsWith(".10")){(!this.latestY||this.latestY.length!==o)&&(this.latestY=new Uint16Array(o));for(let d=0;d<i;d++){const g=l+d*f;for(let y=0;y<s;y++){const w=p<s?Math.floor(y*p/s):y,x=h[g+w*v+1];this.latestY[d*s+y]=x<2?0:Math.min(1023,Math.round(x*3.435+64))}}r=!0}else{(!this.latestY||this.latestY.length!==o)&&(this.latestY=new Uint8Array(o));for(let d=0;d<i;d++){const g=l+d*f;for(let y=0;y<s;y++){const w=p<s?Math.floor(y*p/s):y,x=h[g+w*v+1];this.latestY[d*s+y]=x<2?0:Math.min(255,Math.round(x/1.164+16))}}r=!1}}else{const h=n.includes("10")||n.includes("12"),v=this.configuredCodec&&this.configuredCodec.endsWith(".10");if(h){(!this.latestY||this.latestY.length!==o)&&(this.latestY=new Uint16Array(o));const p=f/2,m=new Uint16Array(this.copyBuf);if(p===s)this.latestY.set(new Uint16Array(this.copyBuf,l,o));else for(let d=0;d<i;d++)this.latestY.set(m.subarray(l/2+d*p,l/2+d*p+s),d*s);r=!0}else if(v){(!this.latestY||this.latestY.length!==o)&&(this.latestY=new Uint16Array(o));const p=new Uint8Array(this.copyBuf);for(let m=0;m<i;m++)for(let d=0;d<s;d++)this.latestY[m*s+d]=p[l+m*f+d]<<2;r=!0,this._upscaleLogged||(b(`Depth: upscaling 8-bit ${n} → 10-bit (source is 10-bit AV1)`,"info"),this._upscaleLogged=!0)}else{(!this.latestY||this.latestY.length!==o)&&(this.latestY=new Uint8Array(o));const p=new Uint8Array(this.copyBuf);if(f===s)this.latestY.set(new Uint8Array(this.copyBuf,l,o));else for(let m=0;m<i;m++)this.latestY.set(p.subarray(l+m*f,l+m*f+s),m*s)}}}else{const a=this.configuredCodec&&this.configuredCodec.endsWith(".10");this.fmtLogged||(b(`Depth frame: fmt=null, ${s}x${i}, canvas fallback${a?" (10-bit via BT.709 reversal)":""}`,"info"),this.fmtLogged=!0);let c;try{(!this.offCanvas||this.offCanvas.width!==s||this.offCanvas.height!==i)&&(this.offCanvas=typeof OffscreenCanvas<"u"?new OffscreenCanvas(s,i):document.createElement("canvas"),this.offCanvas instanceof OffscreenCanvas||(this.offCanvas.width=s,this.offCanvas.height=i),this.offCtx=this.offCanvas.getContext("2d",{willReadFrequently:!0}));const l=await createImageBitmap(e);this.offCtx.drawImage(l,0,0),l.close(),c=this.offCtx.getImageData(0,0,s,i).data}catch{try{this.offCtx.drawImage(e,0,0),c=this.offCtx.getImageData(0,0,s,i).data}catch(f){this._canvasWarnLogged||(b(`Depth canvas fallback failed: ${f.message}`,"error"),this._canvasWarnLogged=!0);return}}if(a){(!this.latestY||this.latestY.length!==o)&&(this.latestY=new Uint16Array(o));for(let l=0;l<o;l++){const f=c[l*4];this.latestY[l]=f<2?0:Math.min(1023,Math.round(f*3.435+64))}r=!0}else{(!this.latestY||this.latestY.length!==o)&&(this.latestY=new Uint8Array(o));for(let l=0;l<o;l++)this.latestY[l]=c[l*4];r=!1}}this.is10bit=r,this.width=s,this.height=i}finally{e.close()}}destroy(){if(this.decoder)try{this.decoder.close()}catch{}this.decoder=null,this.configured=!1,this.latestY=null,this._initSegment=null,this._mseRafId&&cancelAnimationFrame(this._mseRafId),this._msePlayer&&this._msePlayer.destroy(),this._mseVideo&&this._mseVideo.parentElement&&this._mseVideo.remove(),this._msePlayer=null,this._mseVideo=null,this._mseCanvas=null,this._mseCtx=null,this._useMse=!1}}class X{constructor(e,s,{liveMode:i=!0}={}){this.video=e,this.label=s,this.ms=null,this.sb=null,this.queue=[],this.ready=!1,this.frames=0,this.seekIv=null,this.liveMode=i}onData(e){if(this.frames++,!this.ready){if(jt(e)){const s=Te(e);if(!s){b(`${this.label}: cannot detect codec`,"error");return}this.initMse(s,e);return}this.queue.push(e);return}this.enqueue(e)}initMse(e,s){const i=`video/mp4; codecs="${e}"`;if(b(`${this.label}: ${i}`,"data"),!nt.isTypeSupported(i)){const o=e.startsWith("av01"),n=/Safari/.test(navigator.userAgent)&&!/Chrome/.test(navigator.userAgent);b(o&&n?`${this.label}: AV1 not supported in MSE on this Safari/device. AV1 requires M3+ chip or newer. Server needs H.264 encoding for Safari compatibility.`:`${this.label}: codec unsupported: ${e}`,"error");return}this.ms=new nt,window.ManagedMediaSource&&this.ms instanceof ManagedMediaSource?(this.video.disableRemotePlayback=!0,this.video.srcObject=this.ms):this.video.src=URL.createObjectURL(this.ms),this.ms.addEventListener("sourceopen",()=>{try{this.sb=this.ms.addSourceBuffer(i),this.sb.mode="segments",this.sb.addEventListener("updateend",()=>this.flush()),this.ready=!0,this.queue.unshift(s),this.flush(),this.liveMode&&(this.video.play().catch(()=>{}),this.seekIv=setInterval(()=>{if(this.video.buffered.length>0){const o=this.video.buffered.start(0),n=this.video.buffered.end(this.video.buffered.length-1);(this.video.currentTime<o||n-this.video.currentTime>.15)&&(this.video.currentTime=Math.max(o,n-.03))}},100))}catch(o){b(`${this.label}: init failed: ${o.message}`,"error")}})}enqueue(e){this.queue.push(e),this.liveMode&&this.queue.length>3&&this.queue.splice(1,this.queue.length-2),this.flush()}flush(){if(!(!this.sb||this.sb.updating)){if(this.liveMode&&this.video.buffered.length>0){const e=this.video.buffered.start(0),s=this.video.buffered.end(this.video.buffered.length-1);if(s-e>4){try{this.sb.remove(e,s-2)}catch{}return}}if(this.queue.length)try{this.sb.appendBuffer(this.queue.shift())}catch(e){b(`${this.label}: ${e.message}`,"error")}}}destroy(){if(this.seekIv&&clearInterval(this.seekIv),this.queue=[],this.ready=!1,this.ms&&this.ms.readyState==="open")try{this.ms.endOfStream()}catch{}this.video.src="",this.video.srcObject=null}}class je{constructor(e,s){this.video=e,this.latestY=null,this.width=0,this.height=0,this.is10bit=!!(s&&s.endsWith(".10")),this._canvas=null,this._ctx=null,this._rafId=null,this._lastTime=-1,this._startLoop()}_startLoop(){const e=()=>{this._rafId=requestAnimationFrame(e),this._extract()};this._rafId=requestAnimationFrame(e)}_extract(){const e=this.video;if(!e||!e.videoWidth||e.readyState<2||e.currentTime===this._lastTime)return;this._lastTime=e.currentTime;const s=e.videoWidth,i=e.videoHeight;(!this._canvas||this._canvas.width!==s||this._canvas.height!==i)&&(this._canvas=document.createElement("canvas"),this._canvas.width=s,this._canvas.height=i,this._ctx=this._canvas.getContext("2d",{willReadFrequently:!0})),this._ctx.drawImage(e,0,0,s,i);const o=this._ctx.getImageData(0,0,s,i).data,n=s*i;if(this.is10bit){(!this.latestY||this.latestY.length!==n)&&(this.latestY=new Uint16Array(n));for(let r=0;r<n;r++){const a=o[r*4];this.latestY[r]=a<2?0:Math.min(1023,Math.round(a*3.435+64))}}else{(!this.latestY||this.latestY.length!==n)&&(this.latestY=new Uint8Array(n));for(let r=0;r<n;r++)this.latestY[r]=o[r*4]}this.width=s,this.height=i}forceExtract(){this._lastTime=-1,this._extract()}destroy(){this._rafId&&cancelAnimationFrame(this._rafId),this._rafId=null,this.video=null,this._canvas=null,this._ctx=null,this.latestY=null}}const an={fx:920,fy:920,ppx:640,ppy:360,width:1280,height:720};function cn(t,e,s,i,o,n,r){if(!t.videoWidth||!e||!e.latestY)return;const a=e.width,c=e.height,l=1,f=r||an,u=f.fx*a/f.width,h=f.fy*c/f.height,v=f.ppx*a/f.width,p=f.ppy*c/f.height;(s.canvas.width!==a||s.canvas.height!==c)&&(s.canvas.width=a,s.canvas.height=c),s.drawImage(t,0,0,a,c);const m=e.latestY,d=s.getImageData(0,0,a,c).data;let g=0;for(let y=0;y<c;y+=l)for(let w=0;w<a;w+=l){const x=y*a+w,$=m[x];if($<2)continue;const E=e.is10bit?$:$<<4,M=g*3;i[M]=-(w-v)*E/u,i[M+1]=-(y-p)*E/h,i[M+2]=E;const A=(y*a+w)*4;o[M]=d[A]/255,o[M+1]=d[A+1]/255,o[M+2]=d[A+2]/255,g++}n.attributes.position.needsUpdate=!0,n.attributes.color.needsUpdate=!0,n.setDrawRange(0,g),n.computeBoundingSphere()}function Ee(t){const e=(t.general.certHash||"").trim(),s=/Safari/.test(navigator.userAgent)&&!/Chrome/.test(navigator.userAgent),n={websocket:{delay:/firefox/i.test(navigator.userAgent)||s?0:2e3}};if(e){const r=e.replace(/[^0-9a-fA-F]/g,""),a=new Uint8Array(r.length/2);for(let c=0;c<a.length;c++)a[c]=parseInt(r.substr(c*2,2),16);n.webtransport={serverCertificateHashes:[{algorithm:"sha-256",value:a.buffer}]},b(`Using cert hash: ${r.slice(0,16)}...`,"data",{toast:!1})}return n}const Ve=300;function ln(t){return t.replace(/\/(state|commands)$/,"")}async function dn(t,e,s,i,o){var l;const r=`${t.general.relay}/${ln(i)}/state`,a=Ee(t);b(`[${s}] Connecting to ${r}...`,"info",{toast:!1});const c=await Promise.race([$e(new URL(r),a),new Promise((f,u)=>setTimeout(()=>u(new Error(`[${s}] connection timeout`)),8e3))]);b(`[${s}] Connected`,"success");try{const f=c.consume(ie(""));for(;e.running;){const u=f.subscribe("can",0);try{for(;e.running;){const h=await u.nextGroup();if(!h)break;for(;e.running;){const v=await h.readFrame();if(!v)break;const p=new Uint8Array(v);e.bytesTotal+=p.length,(l=e.recorder)==null||l.onData(`can_${s}`,p,"can");const m=Gt(p);e.frameCount+=m.length,e.fpsCounter+=m.length;for(const d of m){const g=Nt(d.canId);if(g<0)continue;const y=Jt(d.data);y&&(o[g].targetAngle=y.qRad,o[g].velocity=y.vel,o[g].torque=y.tau,o[g].tempMos=y.tempMos,o[g].tempRotor=y.tempRotor,o[g].updated=!0)}}}}catch{if(!e.running)break;b(`[${s}] Track reset, re-subscribing in 5s...`,"data",{toast:!1}),await new Promise(v=>setTimeout(v,5e3))}}}finally{try{c.close()}catch{}}}async function fn(t,e,s,i,o){let n=null;for(;e.running;){try{if(await dn(t,e,s,i,o),!e.running)break;n=null,b(`[${s}] Stream ended`,"info")}catch(r){if(!e.running)break;n&&b(`[${s}] ${r.message}`,"error"),n=r}if(!e.running)break;await new Promise(r=>setTimeout(r,Ve))}}async function hn(t,e,s,i,o,n){var u;const a=`${t.general.relay}/${i}`;s.colorPlayer=new X(o,n);const c=Ee(t);b(`[${n}] Connecting to ${a}...`,"info",{toast:!1}),s.conn=await $e(new URL(a),c),b(`[${n}] Connected`,"success");const f=s.conn.consume(ie("")).subscribe("video",0);for(b(`[${n}] Subscribed to video track`,"success",{toast:!1});s.running;){const h=await f.nextGroup();if(!h){b(`[${n}] video track ended`);break}for(;s.running;){const v=await h.readFrame();if(!v)break;const p=new Uint8Array(v);s.colorPlayer.onData(p),(u=e.recorder)==null||u.onData(`${n}_color`,p,"fmp4")}}}function Qt(t){if(t.conn){try{t.conn.close()}catch{}t.conn=null}t.colorPlayer&&(t.colorPlayer.destroy(),t.colorPlayer=null)}async function un(t,e,s,i,o,n){if(!i)return;s.running=!0;let r=null;for(;s.running;){try{if(await hn(t,e,s,i,o,n),!s.running)break;r=null,b(`[${n}] Stream ended`,"info")}catch(a){if(!s.running)break;r&&b(`[${n}] ${a.message}`,"error"),r=a}if(Qt(s),!s.running)break;await new Promise(a=>setTimeout(a,Ve))}}async function mn(t,e,s,i){const o=[];t.cameras.forEach((n,r)=>{const a=(n.path||"").trim();n.enabled!==!1&&a&&s[r]&&o.push(un(t,e,s[r],a,i[r],n.label||"Cam "+(r+1)))}),o.length&&await Promise.all(o)}function ct(t){for(const e of t)e.running=!1,Qt(e)}async function pn(t,e,s,i,o,n){const a=`${t.general.relay}/${i}`;s.colorPlayer=new X(o,n+" Color"),s.depthDecoder=new Ie;const c=Ee(t);b(`[${n}] Connecting to ${a}...`,"info",{toast:!1}),s.conn=await $e(new URL(a),c),b(`[${n}] Connected`,"success");const l=s.conn.consume(ie("")),f=l.subscribe("video",0),u=["video"];async function h(p,m,d,g=!0){for(;s.running;){const y=await p.nextGroup();if(!y){b(`[${n}] ${d} track ended`);break}for(;s.running;){const w=await y.readFrame();if(!w)break;const x=new Uint8Array(w);m(g?rn(x,e.latency):x)}}}const v=[h(f,p=>{var m;e.videoFps.count++,s.colorPlayer.onData(p),(m=e.recorder)==null||m.onData(`${n}_color`,p,"fmp4")},"video")];if(st){const p=l.subscribe("depth",0);v.push(h(p,d=>{var g;s.depthDecoder.onData(d),(g=e.recorder)==null||g.onData(`${n}_depth`,d,"fmp4")},"depth")),u.push("depth");const m=l.subscribe("metadata",0);v.push(h(m,d=>{var g;(g=e.recorder)==null||g.onData(`${n}_meta`,d,"metadata");try{const y=new TextDecoder().decode(d),w=JSON.parse(y);w.fx&&w.fy&&(s.intrinsics=w,s._intrinsicsLogged||(b(`[${n}] Intrinsics: ${w.width}x${w.height} fx=${w.fx} fy=${w.fy} ppx=${w.ppx} ppy=${w.ppy}`,"data",{toast:!1}),s._intrinsicsLogged=!0)),w.gravity&&(s.gravity=w.gravity)}catch(y){console.warn("metadata parse error:",y)}},"metadata",!1)),u.push("metadata")}b(`[${n}] Subscribed to ${u.join(" + ")} tracks`,"success",{toast:!1}),await Promise.all(v)}function Xt(t){if(t.conn){try{t.conn.close()}catch{}t.conn=null}t.colorPlayer&&(t.colorPlayer.destroy(),t.colorPlayer=null),t.depthDecoder&&(t.depthDecoder.destroy(),t.depthDecoder=null),t.intrinsics=null,t.gravity=null,t._intrinsicsLogged=!1,t._frustumUpdated=!1}async function gn(t,e,s,i,o,n){if(!i)return;s.running=!0;let r=null;for(;s.running;){try{if(await pn(t,e,s,i,o,n),!s.running)break;r=null,b(`[${n}] Stream ended`,"info")}catch(a){if(!s.running)break;r&&b(`[${n}] ${a.message}`,"error"),r=a}if(Xt(s),!s.running)break;await new Promise(a=>setTimeout(a,Ve))}}async function yn(t,e,s,i){const o=[];t.realsense.forEach((n,r)=>{const a=(n.path||"").trim();n.enabled!==!1&&a&&s[r]&&o.push(gn(t,e,s[r],a,i[r],n.label||"RS "+(r+1)))}),o.length&&await Promise.all(o)}function lt(t,e){for(const s of t)s.running=!1,Xt(s);e.forEach(s=>s.geometry.setDrawRange(0,0))}function vn(){return{conn:null,audioCtx:null,nextPlayTime:0,running:!1,enabled:!1,framesReceived:0}}function bn(t){if(t.byteLength<20)return null;const e=new DataView(t.buffer,t.byteOffset,t.byteLength);return{sampleRate:e.getUint32(0,!0),channels:e.getUint16(4,!0),sampleFormat:e.getUint16(6,!0),frameCount:e.getUint32(8,!0),timestampUs:e.getUint32(12,!0),dataLength:e.getUint32(16,!0)}}function Zt(t,e,s,i){const o=s*i;if(e===1)return new Float32Array(t.buffer,t.byteOffset,o);const n=new Int16Array(t.buffer,t.byteOffset,o),r=new Float32Array(o);for(let a=0;a<o;a++)r[a]=n[a]/32768;return r}function wn(t,e,s){const i=t.audioCtx;if(!i)return;const o=Zt(s,e.sampleFormat,e.frameCount,e.channels),n=i.createBuffer(e.channels,e.frameCount,e.sampleRate);if(e.channels===1)n.copyToChannel(o,0);else for(let a=0;a<e.channels;a++){const c=new Float32Array(e.frameCount);for(let l=0;l<e.frameCount;l++)c[l]=o[l*e.channels+a];n.copyToChannel(c,a)}const r=i.createBufferSource();r.buffer=n,r.connect(i.destination),t.nextPlayTime<i.currentTime&&(t.nextPlayTime=i.currentTime),r.start(t.nextPlayTime),t.nextPlayTime+=n.duration}async function xn(t,e){const s=t.general.relay,i=(t.audio.path||"").trim();if(!i)return;const o=`${s}/${i}`,n=Ee(t);b(`[audio] Connecting to ${o}...`,"info",{toast:!1}),e.conn=await $e(new URL(o),n),b("[audio] Connected","success");const a=e.conn.consume(ie("")).subscribe("mic",0);for(b('[audio] Subscribed to "mic" track',"success",{toast:!1});e.running;){const c=await a.nextGroup();if(!c){b("[audio] Track ended");break}for(;e.running;){const l=await c.readFrame();if(!l)break;const f=new Uint8Array(l),u=bn(f);if(!u||f.byteLength<20+u.dataLength)continue;e.audioCtx||(e.audioCtx=new AudioContext({sampleRate:u.sampleRate}),e.nextPlayTime=0,b(`[audio] AudioContext created: ${u.sampleRate}Hz, ${u.channels}ch`,"data",{toast:!1})),e.audioCtx.state==="suspended"&&await e.audioCtx.resume();const h=f.subarray(20,20+u.dataLength);wn(e,u,h),e.framesReceived++;const v=Zt(h,u.sampleFormat,u.frameCount,u.channels);let p=0;for(let x=0;x<v.length;x++)p+=v[x]*v[x],Math.abs(v[x]);const m=Math.sqrt(p/v.length),d=m>0?Math.max(-60,20*Math.log10(m)):-60,g=Math.round((d+60)/60*6),y="█".repeat(Math.max(0,g))+"░".repeat(6-Math.max(0,g)),w=document.getElementById("audioLevel");w&&(w.textContent=y,w.style.color=d>-10?"#f44":d>-30?"#4f4":"#555")}}}function es(t){if(t.conn){try{t.conn.close()}catch{}t.conn=null}}async function ts(t,e){e.running=!0;let s=null;for(;e.running;){try{if(await xn(t,e),!e.running)break;s=null,b("[audio] Stream ended","info")}catch(i){if(!e.running)break;b(`[audio] ${i.message}`,"error"),s=i}if(es(e),!e.running)break;await new Promise(i=>setTimeout(i,Ve))}}function He(t){if(t.running=!1,es(t),t.audioCtx){try{t.audioCtx.close()}catch{}t.audioCtx=null}t.nextPlayTime=0,t.framesReceived=0;const e=document.getElementById("audioLevel");e&&(e.textContent="--",e.style.color="#555")}const Cn=5,Ct=["#a855f7","#f97316","#22d3ee","#f43f5e","#84cc16","#eab308","#ec4899","#14b8a6","#6366f1","#fb923c"];function ss(t){let e=0;for(let s=0;s<t.length;s++)e=e*31+t.charCodeAt(s)|0;return Ct[Math.abs(e)%Ct.length]}function $n(){return{conn:null,broadcast:null,track:null,announced:null,subscribers:new Map,running:!1,sessionId:Math.random().toString(36).slice(2,8)}}function ns(t){const e=ss(t.name),s=t.name.replace(/</g,"<"),i=(t.text||"").replace(/</g,"<");return`<span style="color:${e};font-weight:bold">${s}</span><span style="color:#fff">: ${i}</span>`}function dt(t,e){const s=document.createElement("div");s.className="toast toast-chat toast-sticky";const i=new Date(t.ts),o=document.createElement("span");o.style.color="#999",o.textContent=i.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"})+" ";const n=document.createElement("span");if(n.style.color=ss(t.name),n.style.fontWeight="bold",n.textContent=t.name,t.html){const r=document.createElement("span");r.innerHTML=`: ${t.html}`,s.append(o,n,r)}else s.append(o,n,`: ${t.text}`);for(e.prepend(s);e.children.length>Cn;)e.lastChild.remove()}function En(t,e,s,i){if(!t.trim())return;const o={name:s(),text:t.trim(),ts:Date.now()};if(e.track)try{e.track.writeJson(o)}catch(n){b(`[chat] Send error: ${n.message}`,"error")}b(null,"data",{toast:!1,html:ns(o)}),dt(o,i)}function os(t,e){return`${e()}_${t.sessionId}`}function Se(t){const e=t.subscribers.size+(t.running?1:0);document.getElementById("viewerCount").textContent=e===1?"1 viewer":`${e} viewers`}async function _n(t){try{for(;t.running&&t.broadcast;){const e=await t.broadcast.requested();if(!e)break;e.track.name==="messages"?(t.track=e.track,b("[chat] Publish track active","success",{toast:!1})):e.track.close(new Error("unknown track"))}}catch(e){t.running&&b(`[chat] Publish error: ${e.message}`,"error")}}async function Mn(t,e,s){try{const i=t.conn.consume(ie(e)),o=i.subscribe("messages",0);for(t.subscribers.set(e,{broadcast:i,track:o}),b(`[chat] Subscribed to ${e}`,"data",{toast:!1});t.running;){const n=await o.readJson();if(!n)break;b(null,"data",{toast:!1,html:ns(n)}),dt(n,s)}}catch(i){t.running&&b(`[chat] ${e}: ${i.message}`,"error")}finally{t.subscribers.delete(e)}}async function Fn(t,e,s){try{for(t.announced=t.conn.announced();t.running;){const i=await t.announced.next();if(!i)break;const o=i.path;o!==ie(os(t,e))&&(i.active&&!t.subscribers.has(o)?(b(`[chat] User joined: ${String(o).replace(/_[a-z0-9]+$/,"")}`,"data"),Mn(t,o,s),Se(t)):i.active||(t.subscribers.delete(o),b(`[chat] User left: ${String(o).replace(/_[a-z0-9]+$/,"")}`,"info"),Se(t)))}}catch(i){t.running&&b(`[chat] Discovery error: ${i.message}`,"error")}}async function kn(t,e,s,i){const o=t.general.relay,n=(t.chat.path||"").trim();if(!n)return;e.running=!0;const r=`${o}/${n}`,a=Ee(t);try{e.conn=await $e(new URL(r),a),b("[chat] Connected","success"),e.broadcast=new $s,e.conn.publish(ie(os(e,s)),e.broadcast),b(`[chat] Publishing as "${s()}"`,"data",{toast:!1}),Se(e),setTimeout(()=>{dt({name:"openarm",html:"Hello! This is our <b>XoQ OpenArm</b> demo running <em>in real time</em> with a <b>real robot</b>. You can make it do something by typing: <b><code>@robot pick up the yellow cube and put it in the green box</code></b>",ts:Date.now()},i)},2e3),_n(e),Fn(e,s,i)}catch(c){b(`[chat] ${c.message}`,"error")}}function ft(t){t.running=!1;for(const[,e]of t.subscribers)try{e.broadcast.close()}catch{}if(t.subscribers.clear(),t.announced){try{t.announced.close()}catch{}t.announced=null}if(t.track){try{t.track.close()}catch{}t.track=null}if(t.broadcast){try{t.broadcast.close()}catch{}t.broadcast=null}if(t.conn){try{t.conn.close()}catch{}t.conn=null}Se(t)}function An(t,e){const s=[];for(let i=0;i<et.length;i++){const o=et[i],n=document.createElement("div");n.className="joint-row",n.innerHTML=`
|
| 4 |
+
<span class="joint-label" style="color:#${o.color.toString(16).padStart(6,"0")}">${o.name}</span>
|
| 5 |
+
<span class="joint-angle" id="${e}-angle-${i}">0.0°</span>
|
| 6 |
+
<span class="joint-vel" id="${e}-vel-${i}">0.0</span>
|
| 7 |
+
<span class="joint-tau" id="${e}-tau-${i}">0.0</span>
|
| 8 |
+
`,t.appendChild(n),s.push({angle:n.querySelector(`#${e}-angle-${i}`),vel:n.querySelector(`#${e}-vel-${i}`),tau:n.querySelector(`#${e}-tau-${i}`)})}return s}const $t=new Be,Et=new Be,_t=new Be,Ke=new Be,Mt=new at,Ft=new at,Dn=new ze(0,1,0),kt=new ze,is=new ze(0,-1,0);function ge(t,e,s){const i=e.position||{},o=e.rotation||{};if(t.position.set(i.x||0,i.y||0,i.z||0),s){const[c,l,f]=s,u=Math.sqrt(c*c+l*l+f*f);if(u>.5){kt.set(c/u,l/u,-f/u),Mt.setFromUnitVectors(kt,is);const h=(o.yaw||0)*Math.PI/180;Ft.setFromAxisAngle(Dn,h),t.quaternion.multiplyQuaternions(Ft,Mt);return}}const n=(o.roll||0)*Math.PI/180,r=(o.pitch||0)*Math.PI/180,a=(o.yaw||0)*Math.PI/180;$t.makeRotationX(n),Et.makeRotationY(r),_t.makeRotationY(a),Ke.multiplyMatrices(_t,$t),Ke.multiply(Et),t.setRotationFromMatrix(Ke)}const Tn={fx:920,fy:920,width:1280,height:720},re=.3,ae=1.023;function rs(t){const e=t.width/2,s=t.height/2,i=re*e/t.fx,o=re*s/t.fy,n=ae*e/t.fx,r=ae*s/t.fy;return new Float32Array([-i,o,re,i,o,re,i,-o,re,-i,-o,re,-n,r,ae,n,r,ae,n,-r,ae,-n,-r,ae])}function In(t){const e=rs(Tn),s=[0,1,1,2,2,3,3,0,4,5,5,6,6,7,7,4,0,4,1,5,2,6,3,7],i=new Yt;return i.setAttribute("position",new Ze(e,3)),i.setIndex(s),new Bs(i,new zs({color:t,opacity:.5,transparent:!0}))}function Sn(t,e,s){const i=t.children.find(n=>n.isLineSegments);if(!i)return;const o=rs(e);i.geometry.attributes.position.array.set(o),i.geometry.attributes.position.needsUpdate=!0}const Ln=-1.0472,At=.044;function Rn(t){return Math.max(0,Math.min(At,At*(t/Ln)))}const Pn=.2;function Un(t){for(const e of t)e.angle+=(e.targetAngle-e.angle)*Pn}function Le(t,e){return!e||e.length===0?t:t+" · "+e.join(" · ")}function Re(t,e="#aaaaaa"){const s=document.createElement("canvas"),i=s.getContext("2d"),o=52,n=`300 ${o}px "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif`;i.font=n;const r=i.measureText(t).width+24;s.width=r,s.height=o+16,i.font=n,i.fillStyle=e,i.globalAlpha=.85,i.textBaseline="middle",i.fillText(t,12,s.height/2);const a=new qs(s);a.minFilter=Vs;const c=new Hs({map:a,transparent:!0,depthTest:!1}),l=new Ys(c);return l.scale.set(r/1e3,s.height/1e3,1),l}function On(t,e,s){const i=document.getElementById("threeCanvas");let o;try{o=new Es({canvas:i,antialias:!0}),o.setPixelRatio(window.devicePixelRatio),o.setClearColor(1710638)}catch(C){b(`WebGL not available: ${C.message} (3D disabled, MoQ still works)`,"error"),o=null}const n=new _s;n.add(new Ms(16777215,.4));const r=new pt(16777215,.8);r.position.set(5,10,7),n.add(r);const a=new pt(4491519,.3);a.position.set(-5,3,-5),n.add(a);const c=new Fs(50,1,.01,100);c.position.set(1,.8,1.2),c.lookAt(0,.4,0);const l=new ks(c,i);l.target.set(0,.4,0),l.enableDamping=!0,l.dampingFactor=.1,l.update();const f=[];t.armPairs.forEach(C=>{const _=C.position||{x:0,y:0,z:0},F=new As(1,10,3355477,2236996);F.position.set(_.x,_.y,_.z),n.add(F),f.push(F)}),n.add(new Ds(.1));const u=1280*720,h=[16720418,2237183,2293538,16776994],v=[16729156,4474111,4521796,16777028],p=[],m=[],d=[],g=t.realsense.map(()=>({conn:null,colorPlayer:null,depthDecoder:null,running:!1}));t.realsense.forEach((C,_)=>{const F=(C.pointSize||2)*.001,k=document.createElement("div");k.className="camera-feed",k.innerHTML=`<span class="cam-label">${C.label||"RS "+(_+1)}</span>`;const L=document.createElement("video");L.autoplay=!0,L.muted=!0,L.playsInline=!0,L.id="rsVideo"+_,k.appendChild(L),s.appendChild(k),m.push(L);const q=new Float32Array(u*3),Z=new Float32Array(u*3),N=new Yt;N.setAttribute("position",new Ze(q,3)),N.setAttribute("color",new Ze(Z,3)),N.setDrawRange(0,0);const ee=new Ts({size:F,vertexColors:!0,sizeAttenuation:!0}),J=new Is(N,ee);J.scale.set(.001,.001,.001);const W=new gt;n.add(W),W.add(J),W.add(new yt(new Ss(.012,12,8),new Ls({color:h[_%h.length]}))),W.add(In(v[_%v.length]));const V=Re(Le(C.label||"RS "+(_+1),C.tags),"#ff8c42");V.position.set(0,.06,0),W.add(V),d.push(W);const O=document.createElement("canvas");O.width=1280,O.height=720;const Q=O.getContext("2d",{willReadFrequently:!0});p.push({posArr:q,colArr:Z,geometry:N,material:ee,points:J,group:W,colorCtx:Q})}),t.realsense.forEach((C,_)=>{d[_]&&ge(d[_],C)});const y=[],w=t.cameras.map(()=>({conn:null,colorPlayer:null,running:!1}));t.cameras.forEach((C,_)=>{const F=document.createElement("div");F.className="camera-feed",F.innerHTML=`<span class="cam-label">${C.label||"Cam "+(_+1)}</span>`;const k=document.createElement("video");k.autoplay=!0,k.muted=!0,k.playsInline=!0,k.id="camVideo"+_,F.appendChild(k),s.appendChild(F),y.push(k)});const x=new Array(t.armPairs.length).fill(null),$=[],E=new Rs;E.loadMeshCb=(C,_,F)=>{C.endsWith(".stl")?new Ps(_).load(C,k=>{F(new yt(k,new Us))},null,k=>F(null,k)):C.endsWith(".dae")?new Os(_).load(C,k=>F(k.scene),null,k=>F(null,k)):F(null,new Error(`Unknown mesh format: ${C}`))},Y("Loading 3D model..."),b("Loading URDF model...");let M=0;t.armPairs.forEach((C,_)=>{const F=new gt;n.add(F),ge(F,C),$.push(F);const k=Re(Le(C.label||"Arm Pair "+(_+1),C.tags),"#00d4ff");k.position.set(0,.85,0),F.add(k),E.load("./assets/openarm_v10.urdf",L=>{x[_]=L,L.rotation.x=-Math.PI/2,F.add(L),M++,M===t.armPairs.length&&(Y("Idle"),b(`${M} robot model(s) loaded`,"success"))},void 0,L=>{b(`URDF load error (pair ${_+1}): ${L}`,"error"),M++,M===t.armPairs.length&&Y("Model load failed")})});function A(){if(!o)return;const C=i.parentElement,_=C.clientWidth,F=C.clientHeight;o.setSize(_,F),c.aspect=_/F,c.updateProjectionMatrix()}return window.addEventListener("resize",A),requestAnimationFrame(A),{renderer:o,scene:n,camera:c,controls:l,canvas:i,robots:x,robotGroups:$,grids:f,rsVideoEls:m,rsCams:g,pointClouds:p,rsCamGroups:d,camVideoEls:y,camState:w,onResize:A}}function Bn(t,e){t.forEach((s,i)=>{Un(s);const o=Math.floor(i/2),n=i%2,r=e[o];if(!r)return;const a=js[n],c=["J1","J2","J3","J4","J5","J6","J7"].map(f=>a+f);for(let f=0;f<7;f++)r.joints[c[f]]&&r.joints[c[f]].setJointValue(s[f].angle);const l=Rn(s[7].angle);r.joints[a+"EE"]&&r.joints[a+"EE"].setJointValue(l)})}function zn(t,e,s){function i(r,a){for(let c=0;c<et.length;c++){const l=a[c],f=(l.angle*180/Math.PI).toFixed(1);r[c].angle.innerHTML=`${f}°`,r[c].vel.textContent=l.velocity.toFixed(1),r[c].tau.textContent=l.torque.toFixed(1)}}t.forEach((r,a)=>{e[a]&&i(e[a],r)}),document.getElementById("frameCount").textContent=s.frameCount,document.getElementById("bytesReceived").textContent=Js(s.bytesTotal);const o=performance.now();o-s.lastFpsTime>=1e3&&(document.getElementById("canFps").textContent=s.fpsCounter,s.fpsCounter=0,s.lastFpsTime=o),document.getElementById("lastUpdate").textContent=new Date().toLocaleTimeString().split(" ")[0];const n=performance.now();if(n-s.videoFps.lastTime>=1e3){s.videoFps.value=s.videoFps.count,s.videoFps.count=0,s.latency.display=s.latency.samples>0?Math.round(s.latency.sum/s.latency.samples):null,s.latency.sum=0,s.latency.samples=0,s.videoFps.lastTime=n;const r=document.getElementById("streamPing"),a=document.getElementById("streamFps");if(s.latency.display!==null&&Date.now()-s.latency.lastUpdate<=5e3){const l=s.latency.display,f=Math.abs(l);r.textContent=(l<0?"~":"")+"ping "+f+"ms",r.style.color=f<50?"#2ed573":f<150?"#ffa502":"#ff4757"}else r.textContent="--",r.style.color="#555";s.videoFps.value!==null?(a.textContent=s.videoFps.value+" fps",a.style.color="#aaa"):(a.textContent="--",a.style.color="#555")}}const Qe=new Ws,Dt=new ze,Tt=new at;function qn(t){document.querySelectorAll('[data-s="realsenseList"] .item-card').forEach((s,i)=>{const o=s.querySelector(".rs-rr"),n=s.querySelector(".rs-rp");if(!o||!n)return;if(t[i]&&t[i].gravity){const[a,c,l]=t[i].gravity,f=Math.sqrt(a*a+c*c+l*l);f>.5&&(Dt.set(a/f,c/f,-l/f),Tt.setFromUnitVectors(Dt,is),Qe.setFromQuaternion(Tt,"ZYX"),o.value=(Qe.x*180/Math.PI).toFixed(1),n.value=(Qe.y*180/Math.PI).toFixed(1)),o.disabled=!0,n.disabled=!0,o.title="Auto (IMU gravity)",n.title="Auto (IMU gravity)"}else o.disabled=!1,n.disabled=!1,o.title="",n.title=""})}function Vn(t,e,s){const{renderer:i,scene:o,camera:n,controls:r,pointClouds:a,rsVideoEls:c,rsCams:l,rsCamGroups:f}=t;let u=0,h=!1;function v(){requestAnimationFrame(v),Bn(e,t.robots),i&&(r.update(),a.forEach((p,m)=>{if(l[m]&&c[m]&&(cn(c[m],l[m].depthDecoder,p.colorCtx,p.posArr,p.colArr,p.geometry,l[m].intrinsics),l[m].intrinsics&&!l[m]._frustumUpdated&&(Sn(p.group,l[m].intrinsics),l[m]._frustumUpdated=!0),l[m].gravity&&s&&s.realsense[m])){if(!h){const[d,g,y]=l[m].gravity,w=Math.sqrt(d*d+g*g+y*y);console.log(`[gravity] raw accel=[${d.toFixed(3)}, ${g.toFixed(3)}, ${y.toFixed(3)}] |a|=${w.toFixed(2)}`),console.log(`[gravity] downCam (pc frame) = [${(d/w).toFixed(3)}, ${(g/w).toFixed(3)}, ${(-y/w).toFixed(3)}]`),h=!0}ge(f[m],s.realsense[m],l[m].gravity)}}),++u%15===0&&qn(l),i.render(o,n))}v()}function Hn(t){window.dumpDepth=function(){const e=t[0]&&t[0].depthDecoder;if(!e||!e.latestY){console.log("No depth data yet");return}const s=e.latestY,i=e.width,o=e.height,n=[0,Math.floor(o/4),Math.floor(o/2),Math.floor(3*o/4),o-1],r={};for(const h of n){r["row_"+h]=[];for(let v=0;v<i;v+=10)r["row_"+h].push(s[h*i+v])}let a=1/0,c=0,l=0;for(let h=0;h<s.length;h++){const v=s[h];v>0&&(l++,v<a&&(a=v),v>c&&(c=v))}let f=null;if(e.copyBuf&&e._rawDiag){const h=e._rawDiag,v=new Uint8Array(e.copyBuf),p=h.layouts[0].stride,m=h.layouts[0].offset;f={row0:[],midRow:[]};const d=Math.floor(o/2);for(let g=0;g<i&&g<h.codedWidth;g+=10){const y=m+0*p+g*4,w=m+d*p+g*4;f.row0.push(Array.from(v.slice(y,y+4))),f.midRow.push(Array.from(v.slice(w,w+4)))}}const u={browser:navigator.userAgent.includes("Firefox")?"Firefox":navigator.userAgent.includes("Chrome")?"Chrome":"Other",format:e.fmtLogged?"see log":"unknown",codec:e.configuredCodec,width:i,height:o,is10bit:e.is10bit,totalPixels:i*o,nonzeroPixels:l,min:a,max:c,rawDiag:e._rawDiag||null,rawSamples:f,samples:r};return console.log(JSON.stringify(u)),navigator.clipboard.writeText(JSON.stringify(u,null,2)).then(()=>console.log("Copied to clipboard")),u}}function R(t,e){return(t[e]<<24|t[e+1]<<16|t[e+2]<<8|t[e+3])>>>0}function ot(t,e){return R(t,e)*4294967296+R(t,e+4)}function ht(t,e){return String.fromCharCode(t[e],t[e+1],t[e+2],t[e+3])}function*ye(t,e,s){let i=e;for(;i+8<=s;){let o=R(t,i);const n=ht(t,i+4);if(o===0?o=s-i:o===1&&i+16<=s&&(o=ot(t,i+8)),o<8||i+o>s)break;yield{type:n,offset:i,size:o},i+=o}}function as(t,e,s,i){for(const o of ye(t,s,i))if(o.type===e)return o;return null}function H(t,e,s,i){const o=as(t,e,s,i);if(!o)return null;const n=o.size>4294967295?16:8;return{offset:o.offset+n,size:o.size-n,boxOffset:o.offset,boxSize:o.size}}function It(t){const e=as(t,"moov",0,t.length);if(!e)throw new Error("No moov box found");const s=e.offset+8,i=e.offset+e.size,o=[];for(const n of ye(t,s,i)){if(n.type!=="trak")continue;const r=n.offset+8,a=n.offset+n.size,c=H(t,"tkhd",r,a);if(!c)continue;const l=t.subarray(c.offset,c.offset+c.size),f=l[0];let u,h,v;f===0?(u=R(l,12),h=R(l,76)>>>16,v=R(l,80)>>>16):(u=R(l,20),h=R(l,88)>>>16,v=R(l,92)>>>16);const p=H(t,"mdia",r,a);if(!p)continue;const m=H(t,"mdhd",p.offset,p.offset+p.size);let d=9e4;if(m){const M=t.subarray(m.offset,m.offset+m.size);d=M[0]===0?R(M,12):R(M,20)}const g=H(t,"hdlr",p.offset,p.offset+p.size);let y="unkn";g&&(y=ht(t,g.offset+8));let w=null,x=!1,$=null;const E=H(t,"minf",p.offset,p.offset+p.size);if(E){const M=H(t,"stbl",E.offset,E.offset+E.size);if(M){const A=H(t,"stsd",M.offset,M.offset+M.size);if(A){const C=A.offset+8,_=A.offset+A.size;for(const F of ye(t,C,_))if($=F.type,F.type==="av01"){const k=F.offset+8+78,L=F.offset+F.size,q=H(t,"av1C",k,L);q&&(w=t.slice(q.offset,q.offset+q.size),w.length>=2&&(x=!!(w[1]>>6&1)))}}}}o.push({trackId:u,handler:y,timescale:d,width:h,height:v,av1cConfig:w,highBitdepth:x,sampleEntryType:$})}return{tracks:o,moovEnd:e.offset+e.size}}function St(t,e){const s=[];for(const i of ye(t,e,t.length)){if(i.type!=="moof")continue;const o=i.offset,n=i.offset+i.size,r=i.offset+8;let a=n,c=0;a+8<=t.length&&ht(t,a+4)==="mdat"&&(c=R(t,a),c===1&&a+16<=t.length&&(c=ot(t,a+8)));let l=0;const f=H(t,"mfhd",r,n);f&&(l=R(t,f.offset+4));for(const u of ye(t,r,n)){if(u.type!=="traf")continue;const h=u.offset+8,v=u.offset+u.size,p=H(t,"tfhd",h,v);if(!p)continue;const m=t.subarray(p.offset,p.offset+p.size),d=m[1]<<16|m[2]<<8|m[3],g=R(m,4);let y=8,w=0,x=0,$=0;d&8&&(y+=8),d&16&&(y+=4),d&32&&(w=R(m,y),y+=4),d&64&&(x=R(m,y),y+=4),d&128&&($=R(m,y),y+=4);const E=H(t,"tfdt",h,v);let M=0;if(E){const O=t.subarray(E.offset,E.offset+E.size);M=O[0]===0?R(O,4):ot(O,4)}const A=H(t,"trun",h,v);if(!A)continue;const C=t.subarray(A.offset,A.offset+A.size),_=C[1]<<16|C[2]<<8|C[3],F=R(C,4);let k=8,L=0;_&1&&(L=R(C,k)|0,k+=4),_&4&&(k+=4);const q=!!(_&256),Z=!!(_&512),N=!!(_&1024),ee=!!(_&2048),J=[];for(let O=0;O<F;O++){const Q=q?R(C,k):w;q&&(k+=4);const We=Z?R(C,k):x;Z&&(k+=4);const Ne=N?R(C,k):$;N&&(k+=4),ee&&(k+=4),J.push({duration:Q,size:We,flags:Ne})}const W=o+L,V=J.reduce((O,Q)=>O+Q.size,0);s.push({seqNum:l,trackId:g,baseDecodeTime:M,samples:J,dataOffset:W,dataSize:V})}}return s}function D(t,e){t.push(e>>>24&255,e>>>16&255,e>>>8&255,e&255)}function B(t,e){t.push(e>>>8&255,e&255)}function I(t,e,s){const i=8+s.length;D(t,i);for(let o=0;o<4;o++)t.push(e.charCodeAt(o));for(let o=0;o<s.length;o++)t.push(s[o])}function _e(t){const e=[],s=[];for(const o of"isom")s.push(o.charCodeAt(0));D(s,0);for(const o of["isom","iso6","cmfc","av01","mp41"])for(const n of o)s.push(n.charCodeAt(0));I(e,"ftyp",s);const i=[];{const o=[];o.push(0,0,0,0),D(o,0),D(o,0),D(o,t.timescale),D(o,0),D(o,65536),B(o,256);for(let r=0;r<10;r++)o.push(0);const n=[65536,0,0,0,65536,0,0,0,1073741824];for(const r of n)D(o,r);for(let r=0;r<24;r++)o.push(0);D(o,2),I(i,"mvhd",o)}{const o=[];{const n=[];n.push(0,0,0,3),D(n,0),D(n,0),D(n,1),D(n,0),D(n,0);for(let a=0;a<8;a++)n.push(0);B(n,0),B(n,0),B(n,0),B(n,0);const r=[65536,0,0,0,65536,0,0,0,1073741824];for(const a of r)D(n,a);D(n,t.width<<16),D(n,t.height<<16),I(o,"tkhd",n)}{const n=[];{const r=[];r.push(0,0,0,0),D(r,0),D(r,0),D(r,t.timescale),D(r,0),B(r,21956),B(r,0),I(n,"mdhd",r)}{const r=[];r.push(0,0,0,0),D(r,0);const a=t.handler==="vide"?"vide":"meta";for(const l of a)r.push(l.charCodeAt(0));for(let l=0;l<12;l++)r.push(0);const c=t.handler==="vide"?"VideoHandler\0":"MetaHandler\0\0";for(const l of c)r.push(l.charCodeAt(0));I(n,"hdlr",r)}{const r=[];if(t.handler==="vide"){const a=[];a.push(0,0,0,1),B(a,0);for(let c=0;c<6;c++)a.push(0);I(r,"vmhd",a)}else I(r,"nmhd",[0,0,0,0]);{const a=[],c=[];c.push(0,0,0,0),D(c,1),I(c,"url ",[0,0,0,1]),I(a,"dref",c),I(r,"dinf",a)}{const a=[];{const c=[];if(c.push(0,0,0,0),D(c,1),t.handler==="vide"&&t.av1cConfig){const l=[];for(let h=0;h<6;h++)l.push(0);B(l,1),B(l,0),B(l,0);for(let h=0;h<12;h++)l.push(0);B(l,t.width),B(l,t.height),D(l,4718592),D(l,4718592),D(l,0),B(l,1);const f="xoq-replay";l.push(f.length);for(const h of f)l.push(h.charCodeAt(0));for(let h=0;h<31-f.length;h++)l.push(0);B(l,24),l.push(255,255);const u=[];for(let h=0;h<t.av1cConfig.length;h++)u.push(t.av1cConfig[h]);I(l,"av1C",u),I(c,"av01",l)}else{const l=[];for(let u=0;u<6;u++)l.push(0);B(l,1);const f="application/octet-stream\0";for(const u of f)l.push(u.charCodeAt(0));I(c,"mett",l)}I(a,"stsd",c)}for(const c of["stts","stsc","stsz","stco"]){const l=[];l.push(0,0,0,0),D(l,0),c==="stsz"&&D(l,0),I(a,c,l)}I(r,"stbl",a)}I(n,"minf",r)}I(o,"mdia",n)}I(i,"trak",o)}{const o=[],n=[];n.push(0,0,0,0),D(n,1),D(n,1),D(n,0),D(n,0),D(n,0),I(o,"trex",n),I(i,"mvex",o)}return I(e,"moov",i),new Uint8Array(e)}function Me(t,e,s,i,o=0){const n=s.subarray(t.dataOffset,t.dataOffset+t.dataSize),r=[],a=[];{const p=[];p.push(0,0,0,0),D(p,i),I(a,"mfhd",p)}{const p=[];{const m=[];m.push(0,2,0,0),D(m,1),I(p,"tfhd",m)}{const m=[];m.push(1,0,0,0);const d=Math.max(0,t.baseDecodeTime-o);D(m,Math.floor(d/4294967296)),D(m,d>>>0),I(p,"tfdt",m)}{const m=[];m.push(0,0,15,1),D(m,t.samples.length),D(m,0);for(const d of t.samples)D(m,d.duration),D(m,d.size),D(m,d.flags),D(m,0);I(p,"trun",m)}I(a,"traf",p)}I(r,"moof",a);const c=new Uint8Array(r),l=c.length,f=l+8;for(let p=8;p<c.length-8;p++)if(c[p+4]===116&&c[p+5]===114&&c[p+6]===117&&c[p+7]===110){const m=p+8+4+4;c[m]=f>>>24&255,c[m+1]=f>>>16&255,c[m+2]=f>>>8&255,c[m+3]=f&255;break}const u=new Uint8Array(8),h=8+n.length;u[0]=h>>>24&255,u[1]=h>>>16&255,u[2]=h>>>8&255,u[3]=h&255,u[4]=109,u[5]=100,u[6]=97,u[7]=116;const v=new Uint8Array(l+8+n.length);return v.set(c,0),v.set(u,l),v.set(n,l+8),v}function Lt(t,e,s){const i=[],o=[],n=[],r=[];for(const a of t)if(a.handler==="vide")a.highBitdepth?o.push(a):i.push(a);else if(a.handler==="meta"){const c=e.find(l=>l.trackId===a.trackId);c&&c.dataSize>0&&s[c.dataOffset]===123?n.push(a):r.push(a)}return{videoTracks:i,depthTracks:o,metaTracks:n,canTracks:r}}const le=72;function Yn(t){const e=[],s=t.split(`
|
| 9 |
+
`);let i=-1,o=[];for(const n of s){const r=n.trim();if(!r)continue;const a=r.match(/^\((\d+)\.(\d+)\)\s+\S+\s+([0-9A-Fa-f]+)(##([0-9A-Fa-f])([0-9A-Fa-f]*)|#([0-9A-Fa-f]*))$/);if(!a)continue;const c=parseInt(a[1]),l=parseInt(a[2]),f=c*1e3+l/1e3,u=a[3],h=parseInt(u,16),v=u.length>3;let p=0,m="";a[5]!==void 0?(p=parseInt(a[5],16),m=a[6]||""):m=a[7]||"";const d=new Uint8Array(le),g=new DataView(d.buffer);let y=h;v&&(y|=2147483648),g.setUint32(0,y,!0);const w=m.length>>>1;d[4]=w,d[5]=p;for(let x=0;x<w;x++)d[8+x]=parseInt(m.substr(x*2,2),16);if(f!==i&&o.length>0){const x=new Uint8Array(o.length*le);for(let $=0;$<o.length;$++)x.set(o[$],$*le);e.push({timeMs:i,data:x}),o=[]}i=f,o.push(d)}if(o.length>0){const n=new Uint8Array(o.length*le);for(let r=0;r<o.length;r++)n.set(o[r],r*le);e.push({timeMs:i,data:n})}return e}function Rt(t){const e=Math.floor(t/60),s=Math.floor(t%60);return`${e}:${s.toString().padStart(2,"0")}`}class Wn{constructor({armStates:e,rsCams:s,rsVideoEls:i,pointClouds:o,appState:n,config:r}){this.armStates=e,this.rsCams=s,this.rsVideoEls=i,this.pointClouds=o,this.appState=n,this.config=r,this.fileData=null,this.tracks=null,this.fragments=null,this.classified=null,this.durationMs=0,this._timeOffsetMs=0,this.colorPlayers=[],this.depthDecoders=[],this.colorVideoEls=[],this.depthVideoEls=[],this.canEvents=[],this.metaEvents=[],this.playing=!1,this.speed=1,this.masterVideo=null,this._rafId=null,this._destroyed=!1,this.onTimeUpdate=null,this.onEnded=null}async loadFile(e){b(`Loading recording: ${e.name} (${(e.size/1024/1024).toFixed(1)} MB)`,"info");const s=await e.arrayBuffer();this.fileData=new Uint8Array(s);const{tracks:i,moovEnd:o}=It(this.fileData);this.tracks=i,b(`Found ${i.length} tracks`,"data"),this.fragments=St(this.fileData,o),b(`Found ${this.fragments.length} fragments`,"data"),this.classified=Lt(i,this.fragments,this.fileData);const{videoTracks:n,depthTracks:r,metaTracks:a,canTracks:c}=this.classified;b(`Tracks: ${n.length} video, ${r.length} depth, ${a.length} metadata, ${c.length} CAN`,"data");let l=1/0,f=0;for(const h of[...n,...r,...c]){const v=this.fragments.filter(y=>y.trackId===h.trackId);if(v.length===0)continue;const p=v[0],m=v[v.length-1],d=p.baseDecodeTime/h.timescale*1e3,g=(m.baseDecodeTime+m.samples.reduce((y,w)=>y+w.duration,0))/h.timescale*1e3;d<l&&(l=d),g>f&&(f=g)}this._timeOffsetMs=isFinite(l)?l:0,this.durationMs=f>l?f-l:0,b(`Duration: ${Rt(this.durationMs/1e3)}`,"data");const u=n.length;for(let h=0;h<u;h++){const v=n[h],p=r[h],m=_e(v),d=this.fragments.filter(x=>x.trackId===v.trackId),g=this.rsVideoEls[h];if(!g){b(`No video element for source ${h}`,"error");continue}const y=new X(g,`Replay Color ${h}`,{liveMode:!1});this.colorPlayers.push(y),this.colorVideoEls.push(g),y.onData(m);let w=1;for(const x of d){const $=Me(x,s,this.fileData,w++);y.onData($)}if(h===0&&(this.masterVideo=g),p){const x=_e(p),$=this.fragments.filter(F=>F.trackId===p.trackId),E=document.createElement("video");E.muted=!0,E.playsInline=!0,E.style.cssText="position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;",document.body.appendChild(E),this.depthVideoEls.push(E);const M=new X(E,`Replay Depth ${h}`,{liveMode:!1});this.depthDecoders.push(M),M.onData(x);let A=1;for(const F of $){const k=Me(F,s,this.fileData,A++);M.onData(k)}const C=Te(x),_=new je(E,C);this.rsCams[h]&&(this.rsCams[h].depthDecoder=_)}}for(let h=0;h<c.length;h++){const v=c[h],p=this.fragments.filter(d=>d.trackId===v.trackId),m=v.timescale||1e3;for(const d of p){let g=d.dataOffset,y=d.baseDecodeTime;for(const w of d.samples){const x=this.fileData.subarray(g,g+w.size),$=y/m*1e3-this._timeOffsetMs;this.canEvents.push({timeMs:$,data:x,armIdx:h}),y+=w.duration,g+=w.size}}}this.canEvents.sort((h,v)=>h.timeMs-v.timeMs),this.canEvents.length>0&&b(`CAN: ${this.canEvents.length} events`,"data");for(let h=0;h<a.length;h++){const v=a[h],p=this.fragments.filter(d=>d.trackId===v.trackId),m=v.timescale||1e3;for(const d of p){let g=d.dataOffset,y=d.baseDecodeTime;for(const w of d.samples){const x=this.fileData.subarray(g,g+w.size),$=y/m*1e3-this._timeOffsetMs;try{const E=JSON.parse(new TextDecoder().decode(x));this.metaEvents.push({timeMs:$,json:E,sourceIdx:h})}catch{}y+=w.duration,g+=w.size}}}this.metaEvents.sort((h,v)=>h.timeMs-v.timeMs),this.metaEvents.length>0&&b(`Metadata: ${this.metaEvents.length} events`,"data"),await this._waitForMseReady(),b("Replay loaded","success")}async loadFiles(e){b(`Loading ${e.length} recording files`,"info");const s=[],i=[],o=[];for(const d of e)d.name.endsWith(".bin")?i.push(d):d.name.endsWith(".log")?o.push(d):s.push(d);const n=[];for(const d of s){const g=await d.arrayBuffer(),y=new Uint8Array(g),{tracks:w,moovEnd:x}=It(y),$=St(y,x),E=Lt(w,$,y);n.push({file:d,fileData:y,arrayBuf:g,tracks:w,fragments:$,classified:E}),b(` ${d.name}: ${w.length} tracks, ${$.length} fragments`,"data")}const r=[];for(const d of i){const g=await d.arrayBuffer(),y=new Uint8Array(g),w=new DataView(g),x=[];let $=0;for(;$+12<=y.length;){const M=w.getUint32($,!0),C=w.getUint32($+4,!0)*4294967296+M,_=w.getUint32($+8,!0);if($+=12,$+_>y.length)break;const F=y.subarray($,$+_);x.push({timeMs:C,data:F}),$+=_}const E=/can/i.test(d.name)&&!/meta/i.test(d.name);r.push({file:d,events:x,isCan:E}),b(` ${d.name}: ${x.length} ${E?"CAN":"metadata"} events (.bin)`,"data")}const a=[];for(const d of o){const g=await d.text(),y=Yn(g);a.push({file:d,events:y});const w=y.reduce((x,$)=>x+$.data.length/le,0);b(` ${d.name}: ${y.length} batches, ${w} CAN frames (.log)`,"data")}for(const d of n){let g=1/0;for(const y of d.tracks){const w=d.fragments.filter(x=>x.trackId===y.trackId);if(w.length>0){const x=w[0].baseDecodeTime/y.timescale*1e3;x<g&&(g=x)}}d._fileOffsetMs=isFinite(g)?g:0}this._timeOffsetMs=0;const c=[],l=[],f=[],u=[];for(const d of n){const g=d.file.name.toLowerCase(),y=/depth/i.test(g),w=/color/i.test(g);if(y){for(const x of d.classified.videoTracks)l.push({track:x,...d});for(const x of d.classified.depthTracks)l.push({track:x,...d})}else if(w){for(const x of d.classified.videoTracks)c.push({track:x,...d});for(const x of d.classified.depthTracks)c.push({track:x,...d})}else{for(const x of d.classified.videoTracks)c.push({track:x,...d});for(const x of d.classified.depthTracks)l.push({track:x,...d})}for(const x of d.classified.metaTracks)f.push({track:x,...d});for(const x of d.classified.canTracks)u.push({track:x,...d})}b(`Total: ${c.length} video, ${l.length} depth, ${f.length+r.filter(d=>!d.isCan).length} metadata, ${u.length+r.filter(d=>d.isCan).length+a.length} CAN`,"data"),this.tracks=n.flatMap(d=>d.tracks),this.fragments=n.flatMap(d=>d.fragments),n.length>0&&(this.fileData=n[0].fileData);let h=0;for(const{track:d,fragments:g,_fileOffsetMs:y}of[...c,...l,...u]){const w=g.filter(E=>E.trackId===d.trackId);if(w.length===0)continue;const x=w[w.length-1],$=(x.baseDecodeTime+x.samples.reduce((E,M)=>E+M.duration,0))/d.timescale*1e3-(y||0);$>h&&(h=$)}for(const d of r)if(d.events.length>0){const g=d.events[d.events.length-1].timeMs;g>h&&(h=g)}for(const d of a)if(d.events.length>0){const g=d.events[d.events.length-1].timeMs;g>h&&(h=g)}this.durationMs=h,b(`Duration: ${Rt(this.durationMs/1e3)}`,"data");const v=c.length;for(let d=0;d<v;d++){const{track:g,fileData:y,arrayBuf:w,fragments:x,_fileOffsetMs:$}=c[d],E=this.rsVideoEls[d];if(!E){b(`No video element for source ${d}`,"error");continue}const M=_e(g),A=x.filter(k=>k.trackId===g.trackId),C=Math.round(($||0)/1e3*g.timescale),_=new X(E,`Replay Color ${d}`,{liveMode:!1});this.colorPlayers.push(_),this.colorVideoEls.push(E),_.onData(M);let F=1;for(const k of A){const L=Me(k,w,y,F++,C);_.onData(L)}if(d===0&&(this.masterVideo=E),d<l.length){const{track:k,fileData:L,arrayBuf:q,fragments:Z,_fileOffsetMs:N}=l[d],ee=_e(k),J=Z.filter(Ge=>Ge.trackId===k.trackId),W=Math.round((N||0)/1e3*k.timescale),V=document.createElement("video");V.muted=!0,V.playsInline=!0,V.style.cssText="position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;",document.body.appendChild(V),this.depthVideoEls.push(V);const O=new X(V,`Replay Depth ${d}`,{liveMode:!1});this.depthDecoders.push(O),O.onData(ee);let Q=1;for(const Ge of J){const ws=Me(Ge,q,L,Q++,W);O.onData(ws)}const We=Te(ee),Ne=new je(V,We);this.rsCams[d]&&(this.rsCams[d].depthDecoder=Ne)}}let p=0;for(const{track:d,fileData:g,fragments:y,_fileOffsetMs:w}of u){const x=y.filter(E=>E.trackId===d.trackId),$=d.timescale||1e3;for(const E of x){let M=E.dataOffset,A=E.baseDecodeTime;for(const C of E.samples){const _=g.subarray(M,M+C.size),F=A/$*1e3-(w||0);this.canEvents.push({timeMs:F,data:_,armIdx:p}),A+=C.duration,M+=C.size}}p++}for(const d of r)if(d.isCan){for(const g of d.events)this.canEvents.push({timeMs:g.timeMs,data:g.data,armIdx:p});p++}a.sort((d,g)=>d.file.name.localeCompare(g.file.name));for(const d of a){for(const g of d.events)this.canEvents.push({timeMs:g.timeMs,data:g.data,armIdx:p});b(` ${d.file.name} → arm ${p}`,"data"),p++}this.canEvents.sort((d,g)=>d.timeMs-g.timeMs),this.canEvents.length>0&&b(`CAN: ${this.canEvents.length} events across ${p} sources`,"data");for(let d=0;d<f.length;d++){const{track:g,fileData:y,fragments:w,_fileOffsetMs:x}=f[d],$=w.filter(M=>M.trackId===g.trackId),E=g.timescale||1e3;for(const M of $){let A=M.dataOffset,C=M.baseDecodeTime;for(const _ of M.samples){const F=y.subarray(A,A+_.size),k=C/E*1e3-(x||0);try{const L=JSON.parse(new TextDecoder().decode(F));this.metaEvents.push({timeMs:k,json:L,sourceIdx:d})}catch{}C+=_.duration,A+=_.size}}}let m=f.length;for(const d of r)if(!d.isCan){for(const g of d.events)try{const y=JSON.parse(new TextDecoder().decode(g.data));this.metaEvents.push({timeMs:g.timeMs,json:y,sourceIdx:m})}catch{}m++}this.metaEvents.sort((d,g)=>d.timeMs-g.timeMs),this.metaEvents.length>0&&b(`Metadata: ${this.metaEvents.length} events`,"data"),await this._waitForMseReady(),b("Replay loaded","success")}_waitForMseReady(){return new Promise(e=>{const s=[...this.colorPlayers,...this.depthDecoders.filter(o=>o instanceof X)],i=()=>{if(s.every(n=>{var r;return n.ready&&n.queue.length===0&&!((r=n.sb)!=null&&r.updating)||n.frames===0})){for(const n of s)if(n.ms&&n.ms.readyState==="open")try{n.ms.endOfStream()}catch{}for(const n of[...this.colorVideoEls,...this.depthVideoEls])n.pause(),n.buffered.length>0&&(n.currentTime=n.buffered.start(0));e()}else setTimeout(i,50)};i()})}play(){if(!this._destroyed){this.playing=!0;for(const e of[...this.colorVideoEls,...this.depthVideoEls])e.playbackRate=this.speed,e.play().catch(()=>{});this._startAnimationLoop()}}pause(){this.playing=!1;for(const e of[...this.colorVideoEls,...this.depthVideoEls])e.pause();this._rafId&&(cancelAnimationFrame(this._rafId),this._rafId=null)}seek(e){var o;const i=(e+(this._timeOffsetMs||0))/1e3;for(const n of[...this.colorVideoEls,...this.depthVideoEls])if(n.buffered.length>0){const r=n.buffered.start(0),a=n.buffered.end(n.buffered.length-1);n.currentTime=Math.max(r,Math.min(a,i))}for(let n=0;n<this.rsCams.length;n++){const r=(o=this.rsCams[n])==null?void 0:o.depthDecoder;r&&r.forceExtract&&r.forceExtract()}this._applyCan(e),this._applyMetadata(e)}setSpeed(e){this.speed=e;for(const s of[...this.colorVideoEls,...this.depthVideoEls])s.playbackRate=e}get currentTimeMs(){return this.masterVideo&&this.masterVideo.currentTime>0?this.masterVideo.currentTime*1e3-this._timeOffsetMs:0}_startAnimationLoop(){if(this._rafId)return;const e=()=>{if(this._destroyed||!this.playing)return;this._rafId=requestAnimationFrame(e);const s=this.currentTimeMs;if(this._applyCan(s),this._applyMetadata(s),this.masterVideo&&this.masterVideo.ended){this.playing=!1,this.onEnded&&this.onEnded();return}this.onTimeUpdate&&this.onTimeUpdate(s)};this._rafId=requestAnimationFrame(e)}_applyCan(e){if(this.canEvents.length===0)return;let s=0,i=this.canEvents.length-1;for(;s<i;){const r=s+i+1>>>1;this.canEvents[r].timeMs<=e?s=r:i=r-1}if(this.canEvents[s].timeMs>e)return;const o=Math.max(0,e-100);let n=s;for(;n>0&&this.canEvents[n-1].timeMs>=o;)n--;for(let r=n;r<=s;r++){const a=this.canEvents[r],c=Gt(a.data);for(const l of c){const f=Nt(l.canId);if(f<0)continue;const u=Jt(l.data);if(!u)continue;const h=a.armIdx!==void 0&&this.armStates[a.armIdx]?this.armStates[a.armIdx]:null;if(h)f<h.length&&(h[f].targetAngle=u.qRad,h[f].velocity=u.vel,h[f].torque=u.tau,h[f].tempMos=u.tempMos,h[f].tempRotor=u.tempRotor,h[f].updated=!0);else for(const v of this.armStates)f<v.length&&(v[f].targetAngle=u.qRad,v[f].velocity=u.vel,v[f].torque=u.tau,v[f].tempMos=u.tempMos,v[f].tempRotor=u.tempRotor,v[f].updated=!0)}}}_applyMetadata(e){if(this.metaEvents.length===0)return;const s={};for(const i of this.metaEvents){if(i.timeMs>e)break;s[i.sourceIdx]=i}for(const[i,o]of Object.entries(s)){const n=parseInt(i);if(!this.rsCams[n])continue;const r=o.json;r.fx&&r.fy&&(this.rsCams[n].intrinsics=r),r.gravity&&(this.rsCams[n].gravity=r.gravity)}}destroy(){var e;this._destroyed=!0,this.pause();for(const s of this.colorPlayers)s.destroy();for(const s of this.depthDecoders)s.destroy();for(let s=0;s<this.rsCams.length;s++){const i=(e=this.rsCams[s])==null?void 0:e.depthDecoder;i&&i instanceof je&&i.destroy(),this.rsCams[s].depthDecoder=null,this.rsCams[s].intrinsics=null,this.rsCams[s].gravity=null,this.rsCams[s]._frustumUpdated=!1}for(const s of this.depthVideoEls)s.pause(),s.src="",s.parentNode&&s.parentNode.removeChild(s);for(const s of this.armStates)for(const i of s)i.targetAngle=0,i.velocity=0,i.torque=0,i.tempMos=0,i.tempRotor=0;if(this.pointClouds)for(const s of this.pointClouds)s.geometry.setDrawRange(0,0);this.colorPlayers=[],this.depthDecoders=[],this.colorVideoEls=[],this.depthVideoEls=[],this.fileData=null,this.fragments=null,this.canEvents=[],this.metaEvents=[],b("Replay closed","info")}}function Nn(t){return new Wn(t)}const Xe=72;function Gn(t,e,s){const i=[],o=Math.floor(t/1e3),n=Math.floor(t%1e3*1e3),r=`(${o}.${String(n).padStart(6,"0")})`;let a=0;for(;a+Xe<=s.length;){const l=new DataView(s.buffer,s.byteOffset+a,Xe).getUint32(0,!0),f=Math.min(s[a+4],64),u=s[a+5],h=(l&2147483648)!==0,v=l&536870911,p=h?v.toString(16).toUpperCase().padStart(8,"0"):v.toString(16).toUpperCase().padStart(3,"0");let m="";for(let d=0;d<f;d++)m+=s[a+8+d].toString(16).toUpperCase().padStart(2,"0");u!==0||f>8?i.push(`${r} ${e} ${p}##${u.toString(16).toUpperCase()}${m}`):i.push(`${r} ${e} ${p}#${m}`),a+=Xe}return i.join(`
|
| 10 |
+
`)+(i.length?`
|
| 11 |
+
`:"")}function Pt(t){return t.length>=8&&t[4]===102&&t[5]===116&&t[6]===121&&t[7]===112}function Jn(t,e){return(t[e]<<24|t[e+1]<<16|t[e+2]<<8|t[e+3])>>>0}function jn(t,e){return String.fromCharCode(t[e+4],t[e+5],t[e+6],t[e+7])}function Kn(t){let e=0;for(;e+8<=t.length;){const s=Jn(t,e);if(s<8||e+s>t.length)break;const i=jn(t,e);if(i==="moof"||i==="styp")return t.subarray(e);e+=s}return null}class Qn{constructor(e,s){this.key=e,this.type=s,this.init=null,this.chunks=[]}}class Xn{constructor(){this.recording=!1,this.startTime=0,this.tracks=new Map}start(){this.recording=!0,this.startTime=performance.now();for(const e of this.tracks.values())e.chunks=[];b("Recording started","success")}stop(){this.recording=!1;const e=performance.now()-this.startTime;b(`Recording stopped (${(e/1e3).toFixed(1)}s)`,"success");const s=this._buildFiles();this._downloadAll(s);for(const i of this.tracks.values())i.chunks=[];return s}onData(e,s,i){let o=this.tracks.get(e);if(o||(o=new Qn(e,i||"fmp4"),this.tracks.set(e,o)),o.type==="fmp4"){if(Pt(s)&&(o.init=new Uint8Array(s)),!this.recording)return;o.chunks.push({data:new Uint8Array(s)})}else{if(!this.recording)return;const n=performance.now()-this.startTime;o.chunks.push({timeMs:n,data:new Uint8Array(s)})}}_buildFiles(){const e=new Date,s=String(e.getHours()).padStart(2,"0")+String(e.getMinutes()).padStart(2,"0")+String(e.getSeconds()).padStart(2,"0"),i=[];for(const[o,n]of this.tracks){if(n.chunks.length===0&&!n.init)continue;const r=o.replace(/[^a-zA-Z0-9_-]/g,"_");if(n.type==="fmp4"){if(n.chunks.length===0)continue;const a=[];let c=!1;for(const h of n.chunks)if(Pt(h.data))if(!c)a.push(h.data),c=!0;else{const v=Kn(h.data);v&&a.push(v)}else!c&&n.init&&(a.push(n.init),c=!0),a.push(h.data);if(a.length===0)continue;const l=a.reduce((h,v)=>h+v.length,0),f=new Uint8Array(l);let u=0;for(const h of a)f.set(h,u),u+=h.length;i.push({blob:new Blob([f],{type:"video/mp4"}),filename:`rec_${s}_${r}.mp4`}),b(`Built fmp4 track: ${o} (${n.chunks.length} segments, ${(l/1024).toFixed(0)} KB)`,"data")}else if(n.type==="can"){const a=o.replace(/^can_/,"")||"can0",c=[];for(const f of n.chunks)c.push(Gn(f.timeMs,a,f.data));const l=c.join("");i.push({blob:new Blob([l],{type:"text/plain"}),filename:`rec_${s}_${r}.log`}),b(`Built can track: ${o} (${n.chunks.length} batches)`,"data")}else{let a=0;for(const u of n.chunks)a+=12+u.data.length;const c=new Uint8Array(a),l=new DataView(c.buffer);let f=0;for(const u of n.chunks){const h=Math.round(u.timeMs);l.setUint32(f,h>>>0,!0),l.setUint32(f+4,h/4294967296>>>0,!0),f+=8,l.setUint32(f,u.data.length,!0),f+=4,c.set(u.data,f),f+=u.data.length}i.push({blob:new Blob([c],{type:"application/octet-stream"}),filename:`rec_${s}_${r}.bin`}),b(`Built metadata track: ${o} (${n.chunks.length} entries)`,"data")}}return i}_downloadAll(e){for(const{blob:s,filename:i}of e){const o=URL.createObjectURL(s),n=document.createElement("a");n.href=o,n.download=i,document.body.appendChild(n),n.click(),document.body.removeChild(n),setTimeout(()=>URL.revokeObjectURL(o),5e3),b(`Downloaded: ${i} (${(s.size/1024).toFixed(0)} KB)`,"data")}}}const S=xs({urlOverrides:!0}),ve=Ht(S),Zn=document.getElementById("log"),ut=document.getElementById("toasts"),eo=document.getElementById("statusText");Gs(Zn,ut,eo);const be=document.getElementById("chatUsername");be.value=localStorage.getItem("chatUsername")||"anon-"+Math.random().toString(36).slice(2,6);localStorage.setItem("chatUsername",be.value);be.addEventListener("change",()=>localStorage.setItem("chatUsername",be.value));const T={running:!1,connections:[],frameCount:0,bytesTotal:0,fpsCounter:0,lastFpsTime:performance.now(),latency:{ms:null,lastUpdate:0,sum:0,samples:0,display:null},videoFps:{count:0,value:null,lastTime:performance.now()}},Ye=ve.map(()=>Ks()),P=vn(),we=$n();function cs(){return be.value.trim()||"Anon"}const ls=document.getElementById("cameraSplit"),ds=On(S,ve,ls),{robotGroups:j,grids:de,pointClouds:ne,rsVideoEls:he,rsCams:oe,rsCamGroups:K,camVideoEls:xe,camState:Pe,onResize:fs}=ds;window._rsCams=oe;window._pointClouds=ne;S.armPairs.forEach((t,e)=>{const s=t.enabled!==!1;j[e]&&(j[e].visible=s),de[e]&&(de[e].visible=s)});S.realsense.forEach((t,e)=>{const s=t.enabled!==!1;K[e]&&(K[e].visible=s);const i=he[e]&&he[e].parentElement;i&&(i.style.display=s?"":"none")});S.cameras.forEach((t,e)=>{const s=t.enabled!==!1,i=xe[e]&&xe[e].parentElement;i&&(i.style.display=s?"":"none")});const Ue=document.getElementById("settingsPanel"),hs=document.getElementById("settingsToggle");let ce=!1;const us=Cs(Ue,S,{primaryButtonLabel:"Close Settings",onPrimaryAction(){ms()},onLiveUpdate(t,e){if(t==="armPair"&&j[e]){const s=S.armPairs[e];if(ge(j[e],s),de[e]){const n=s.position||{};de[e].position.set(n.x||0,n.y||0,n.z||0)}const i=j[e].children.find(n=>n.isSprite);i&&(j[e].remove(i),i.material.map.dispose(),i.material.dispose());const o=Re(Le(s.label||"Arm Pair "+(e+1),s.tags),"#00d4ff");o.position.set(0,.85,0),j[e].add(o)}if(t==="realsense"&&K[e]){const s=S.realsense[e];ge(K[e],s);const i=K[e].children.find(n=>n.isSprite);i&&(K[e].remove(i),i.material.map.dispose(),i.material.dispose());const o=Re(Le(s.label||"RS "+(e+1),s.tags),"#ff8c42");o.position.set(0,.06,0),K[e].add(o),ne[e]&&(ne[e].material.size=(s.pointSize||2)*.001)}},onStructuralChange(){S.armPairs.forEach((t,e)=>{const s=t.enabled!==!1;j[e]&&(j[e].visible=s),de[e]&&(de[e].visible=s)}),S.realsense.forEach((t,e)=>{const s=t.enabled!==!1;K[e]&&(K[e].visible=s);const i=he[e]&&he[e].parentElement;i&&(i.style.display=s?"":"none")}),S.cameras.forEach((t,e)=>{const s=t.enabled!==!1,i=xe[e]&&xe[e].parentElement;i&&(i.style.display=s?"":"none")}),b("Config saved. Disconnect and reconnect to apply path changes.","info")}});function ms(){ce=!ce,Ue.classList.toggle("active",ce),hs.classList.toggle("active",ce),ls.classList.toggle("settings-hidden",ce),ce&&us.renderAll(),requestAnimationFrame(fs)}hs.addEventListener("click",ms);const ps=[],gs=[],Ut=document.getElementById("armPanelsContainer"),to=[...new Set(ve.map(t=>t.pairIdx))];to.forEach(t=>{const e=S.armPairs[t],s=ve.filter(o=>o.pairIdx===t),i=document.createElement("div");i.style.cssText="display:flex; align-items:center; gap:0.5rem; padding:0.5rem 0.75rem 0; border-bottom:none;",i.innerHTML=`<span style="font-size:0.8rem; color:#888; text-transform:uppercase; font-weight:bold;">${e.label||"Arm Pair "+(t+1)}</span>`,Ut.appendChild(i),s.forEach(o=>{const n=ve.indexOf(o),r=document.createElement("div");r.className="panel-section",r.id="panelArm"+n;const a=document.createElement("div");a.style.cssText="display:flex; align-items:center; gap:0.5rem; margin-bottom:0.4rem;",a.innerHTML=`<h3 style="margin:0;">${o.label||"Arm "+(n+1)}</h3>`,r.appendChild(a);const c=document.createElement("div");c.innerHTML=`<div class="joint-row" style="font-size:0.65rem; color:#666;">
|
| 12 |
+
<span>Joint</span><span style="text-align:right;">Angle</span><span style="text-align:right;">Vel</span><span style="text-align:right;">Tau</span>
|
| 13 |
+
</div>
|
| 14 |
+
<div id="armJointRows${n}"></div>`,r.appendChild(c),Ut.appendChild(r),gs.push(r),ps.push(An(r.querySelector(`#armJointRows${n}`),"arm"+n))})});const se=document.getElementById("startBtn"),ue=document.getElementById("stopBtn"),G=document.getElementById("audioBtn"),z=document.getElementById("recordBtn");Vn(ds,Ye,S);Hn(oe);G.addEventListener("click",()=>{P.enabled=!P.enabled,P.enabled?(P.audioCtx||(P.audioCtx=new AudioContext({sampleRate:48e3}),P.nextPlayTime=0,b(`[audio] AudioContext created: ${P.audioCtx.sampleRate}Hz`,"data",{toast:!1})),P.audioCtx.state==="suspended"&&P.audioCtx.resume(),G.classList.add("active"),G.innerHTML="🔊 Audio",T.running&&ts(S,P).catch(t=>b(`[audio] ${t.message}`,"error"))):(G.classList.remove("active"),G.innerHTML="🔇 Audio",He(P))});z.addEventListener("click",()=>{var t;(t=T.recorder)!=null&&t.recording?(T.recorder.stop(),z.innerHTML='<span class="rec-dot"></span> Record',z.classList.remove("recording")):T.recorder&&(T.recorder.start(),z.innerHTML='<span class="rec-dot"></span> Stop',z.classList.add("recording"))});se.addEventListener("click",async()=>{var t;try{const e=Ht(S),s=e.some(c=>c.enabled&&(c.path||"").trim()),i=S.realsense.some(c=>c.enabled!==!1&&(c.path||"").trim()),o=S.cameras.some(c=>c.enabled!==!1&&(c.path||"").trim());if(!s&&!i&&!o){b("No enabled paths configured — open Settings to add arm/camera paths","error");return}se.disabled=!0,ue.disabled=!1,z.disabled=!1,T.recorder=new Xn,T.running=!0,T.frameCount=0,T.bytesTotal=0,T.fpsCounter=0,T.connections=[],Y("Connecting..."),b(`WebTransport: ${typeof WebTransport<"u"?"supported":"NOT supported"}`,"data",{toast:!1});const n=e.map(c=>(c.path||"").trim()).filter(Boolean),r=n.filter((c,l)=>n.indexOf(c)!==l);r.length&&b(`WARNING: Multiple arms share path "${r[0]}" — cross-talk expected!`,"error");const a=[];e.forEach((c,l)=>{const f=(c.path||"").trim();c.enabled&&f&&a.push(fn(S,T,c.id,f,Ye[l]).catch(u=>b(`[${c.id}] ${u.message}`,"error")))}),i&&a.push(yn(S,T,oe,he).catch(c=>b(`[realsense] ${c.message}`,"error"))),o&&a.push(mn(S,T,Pe,xe).catch(c=>b(`[camera] ${c.message}`,"error"))),S.chat.enabled!==!1&&a.push(kn(S,we,cs,ut).catch(c=>b(`[chat] ${c.message}`,"error"))),S.audio.enabled!==!1&&(G.disabled=!1),P.enabled&&(P.audioCtx||(P.audioCtx=new AudioContext({sampleRate:48e3}),P.nextPlayTime=0),P.audioCtx.state==="suspended"&&P.audioCtx.resume(),a.push(ts(S,P).catch(c=>b(`[audio] ${c.message}`,"error")))),Y("Streaming"),await Promise.all(a),Y("Ended")}catch(e){b(`Error: ${e.message}`,"error"),console.error(e),Y("Error")}finally{lt(oe,ne),ct(Pe),ft(we),He(P),(t=T.recorder)!=null&&t.recording&&(T.recorder.stop(),z.innerHTML='<span class="rec-dot"></span> Record',z.classList.remove("recording")),T.recorder=null,G.disabled=!0,z.disabled=!0,se.disabled=!1,ue.disabled=!0}});ue.addEventListener("click",async()=>{var t;T.running=!1,Y("Stopping..."),lt(oe,ne),ct(Pe),ft(we),He(P),T.latency.ms=null,T.latency.lastUpdate=0,T.latency.sum=0,T.latency.samples=0,T.latency.display=null,T.videoFps.count=0,T.videoFps.value=null,(t=T.recorder)!=null&&t.recording&&(T.recorder.stop(),z.innerHTML='<span class="rec-dot"></span> Record',z.classList.remove("recording")),T.recorder=null,G.disabled=!0,z.disabled=!0;for(const e of T.connections)try{e.close()}catch{}T.connections=[],se.disabled=!1,ue.disabled=!0,Y("Disconnected"),b("Disconnected","success")});document.getElementById("chatInput").addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),En(t.target.value,we,cs,ut),t.target.value="")});setInterval(()=>zn(Ye,ps,T),100);let U=null;const mt=document.getElementById("replayBtn"),De=document.getElementById("replayFile"),ys=document.getElementById("replayBar"),fe=document.getElementById("replayPlayPause"),Ce=document.getElementById("replayTimeline"),Oe=document.getElementById("replayCurrentTime"),vs=document.getElementById("replayDuration"),Ot=document.getElementById("replaySpeed"),so=document.getElementById("replayClose");function it(t){const e=Math.floor(t/1e3);return`${Math.floor(e/60)}:${(e%60).toString().padStart(2,"0")}`}mt.addEventListener("click",()=>{U||De.click()});De.addEventListener("change",async()=>{var e;const t=Array.from(De.files);if(t.length!==0){if(De.value="",T.running){T.running=!1,lt(oe,ne),ct(Pe),ft(we),He(P),(e=T.recorder)!=null&&e.recording&&(T.recorder.stop(),z.innerHTML='<span class="rec-dot"></span> Record',z.classList.remove("recording")),T.recorder=null;for(const s of T.connections)try{s.close()}catch{}T.connections=[]}se.disabled=!0,ue.disabled=!0,G.disabled=!0,z.disabled=!0,mt.classList.add("active"),U=Nn({armStates:Ye,rsCams:oe,rsVideoEls:he,pointClouds:ne,appState:T,config:S});try{Y("Loading replay..."),t.length===1?await U.loadFile(t[0]):await U.loadFiles(t),ys.classList.add("active"),vs.textContent=it(U.durationMs),Oe.textContent="0:00",Ce.value=0,fe.textContent="Play",U.onTimeUpdate=s=>{Oe.textContent=it(s),U.durationMs>0&&(Ce.value=Math.round(s/U.durationMs*1e3))},U.onEnded=()=>{fe.textContent="Play"},Y("Replay loaded"),b("Replay ready. Press Play.","success")}catch(s){b(`Replay error: ${s.message}`,"error"),console.error(s),bs()}}});fe.addEventListener("click",()=>{U&&(U.playing?(U.pause(),fe.textContent="Play"):(U.play(),fe.textContent="Pause"))});Ce.addEventListener("input",()=>{if(!U)return;const e=parseInt(Ce.value)/1e3*U.durationMs;U.seek(e),Oe.textContent=it(e)});Ot.addEventListener("change",()=>{U&&U.setSpeed(parseFloat(Ot.value))});function bs(){U&&(U.destroy(),U=null),ys.classList.remove("active"),mt.classList.remove("active"),se.disabled=!1,ue.disabled=!0,G.disabled=!0,fe.textContent="Play",Ce.value=0,Oe.textContent="0:00",vs.textContent="0:00",Y("Idle")}so.addEventListener("click",bs);b("Ready. Click Connect to start.","info");setTimeout(()=>se.click(),500);const Bt=document.getElementById("tabBar"),zt=document.querySelector(".side-panel"),qt=document.getElementById("log"),Vt=document.getElementById("cameraSplit"),me=document.getElementById("view3d"),rt={arms:gs,stats:[document.getElementById("panelStats")]},no=Object.values(rt).flat();Bt.addEventListener("click",t=>{const e=t.target.closest(".tab-btn");if(!e)return;const s=e.dataset.tab;Bt.querySelectorAll(".tab-btn").forEach(i=>i.classList.remove("active")),e.classList.add("active"),zt.classList.remove("tab-visible"),no.forEach(i=>i.classList.remove("tab-visible")),qt.classList.remove("tab-visible"),Vt.classList.remove("tab-visible"),Ue.classList.remove("tab-visible"),me.style.display="",s==="3d"?requestAnimationFrame(fs):s==="log"?(qt.classList.add("tab-visible"),me.style.display="none"):s==="camera"?(Vt.classList.add("tab-visible"),me.style.display="none"):s==="settings"?(Ue.classList.add("tab-visible"),us.renderAll(),me.style.display="none"):s in rt&&(zt.classList.add("tab-visible"),rt[s].forEach(i=>i.classList.add("tab-visible")),me.style.display="none")});
|
assets/openarm-settings-Cing9rdF.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const _={relay:"https://cdn.1ms.ai",certHash:"",queryRate:200,pointSize:2},j=[{id:"pair2",enabled:!0,label:"Baguette Arm",tags:[],leftPath:"anon/7e58263812ba/xoq-can-can0",rightPath:"anon/7e58263812ba/xoq-can-can1",position:{x:0,y:0,z:0},rotation:{roll:0,pitch:0,yaw:0}},{id:"pair3",enabled:!1,label:"champagne-arm",tags:[],leftPath:"anon/a13af1d39199/xoq-can-can0",rightPath:"anon/a13af1d39199/xoq-can-can1",position:{x:0,y:0,z:0},rotation:{roll:0,pitch:0,yaw:0}}],U=[{id:"cam1",enabled:!0,label:"Camera 1",tags:[],path:"anon/debug-image"}],Y={path:"anon/openarm-audio",enabled:!0},G={path:"anon/openarm-chat",username:"anon",enabled:!0},B=[{id:"rs3",enabled:!0,label:"baguette-realsense",tags:[],path:"anon/7e58263812ba/realsense-243222073892",position:{x:-.24,y:.78,z:.3},rotation:{roll:94.9,pitch:-2.3,yaw:-44},showColor:!0,pointSize:2},{id:"rs2",enabled:!1,label:"champagne-realsense",tags:[],path:"anon/a13af1d39199/realsense-233522074606",position:{x:-.22,y:.71,z:.3},rotation:{roll:90,pitch:-45,yaw:1},showColor:!0,pointSize:2}],Q={version:3,general:_,armPairs:j,cameras:U,audio:Y,chat:G,realsense:B},x="openarm.config",H="openarm.",A=4;function C(){return structuredClone(Q)}function X(){const t=(n,o)=>localStorage.getItem(H+n)||o,e=C();return e.general.relay=t("relay",e.general.relay),e.general.certHash=t("certHash",""),e.armPairs[0].queryRate=parseInt(t("queryRate","200"))||200,e.armPairs[0].leftPath=t("left",""),e.armPairs[0].rightPath=t("right",""),e.realsense[0].path=t("depth","anon/realsense"),e.realsense[0].pointSize=parseFloat(t("ptSize","2"))||2,e.realsense[0].position={x:parseFloat(t("camX","-0.33")),y:parseFloat(t("camY","0.84")),z:parseFloat(t("camZ","0.29"))},e.realsense[0].rotation={roll:parseFloat(t("camRoll","90")),pitch:parseFloat(t("camPitch","-222")),yaw:parseFloat(t("camYaw","0"))},e.realsense[1].path=t("depth2",""),e.realsense[1].position={x:parseFloat(t("cam2X","-0.33")),y:parseFloat(t("cam2Y","0.84")),z:parseFloat(t("cam2Z","0.29"))},e.realsense[1].rotation={roll:parseFloat(t("cam2Roll","90")),pitch:parseFloat(t("cam2Pitch","-225")),yaw:parseFloat(t("cam2Yaw","0"))},e.audio.path=t("audioPath","anon/openarm-audio"),e.chat.path=t("chatPath","anon/openarm-chat"),e.chat.username=t("chatUsername","anon"),e}function D(t){if(t.arms&&!t.armPairs){t.armPairs=[];for(let e=0;e<t.arms.length;e+=2){const n=t.arms[e],o=t.arms[e+1];t.armPairs.push({id:"pair"+(Math.floor(e/2)+1),label:"Arm Pair "+(Math.floor(e/2)+1),leftPath:n&&n.path||"",rightPath:o&&o.path||"",position:{x:0,y:0,z:0},rotation:{roll:0,pitch:0,yaw:0},queryRate:200})}delete t.arms}return t}function Z(t){if(t.version>=3)return t;const e=t.general&&t.general.pointSize||2,n=t.general&&t.general.queryRate||200;return t.realsense=(t.cameras||[]).map((o,r)=>({id:o.id||"rs"+(r+1),label:o.label||"RealSense "+(r+1),path:o.path||"",position:o.position||{x:-.33,y:.84,z:.29},rotation:o.rotation||{roll:90,pitch:-222,yaw:0},showColor:o.showColor!==!1,pointSize:e})),t.cameras=[],t.armPairs&&t.armPairs.forEach(o=>{o.queryRate||(o.queryRate=n)}),t.general&&(delete t.general.queryRate,delete t.general.pointSize),t.audio&&delete t.audio.enabled,t.version=3,t}function K(t={}){let e;parseInt(localStorage.getItem("openarm.configRevision")||"0")<A&&(localStorage.removeItem(x),localStorage.setItem("openarm.configRevision",String(A)));const o=localStorage.getItem(x);if(o)try{e=D(JSON.parse(o))}catch{e=C()}else localStorage.getItem(H+"relay")?(e=X(),localStorage.setItem(x,JSON.stringify(e))):e=C();if(e=Z(e),e.armPairs&&e.armPairs.forEach(r=>{r.enabled===void 0&&(r.enabled=!0),r.position||(r.position={x:0,y:0,z:0}),r.rotation||(r.rotation={roll:0,pitch:0,yaw:0}),r.queryRate||(r.queryRate=100),r.autoQuery===void 0&&(r.autoQuery=!1),r.leftPath&&(r.leftPath=r.leftPath.replace(/\/state$/,"")),r.rightPath&&(r.rightPath=r.rightPath.replace(/\/state$/,""))}),e.realsense&&e.realsense.forEach(r=>{r.enabled===void 0&&(r.enabled=!0),r.pointSize||(r.pointSize=2)}),e.cameras||(e.cameras=[]),e.cameras.forEach(r=>{r.enabled===void 0&&(r.enabled=!0)}),e.armPairs&&e.armPairs.forEach(r=>{r.tags||(r.tags=[])}),e.realsense&&e.realsense.forEach(r=>{r.tags||(r.tags=[])}),e.cameras.forEach(r=>{r.tags||(r.tags=[])}),e.audio&&e.audio.enabled===void 0&&(e.audio.enabled=!0),e.chat&&e.chat.enabled===void 0&&(e.chat.enabled=!0),t.urlOverrides){const r=new URLSearchParams(location.search);r.has("relay")&&(e.general.relay=r.get("relay")||""),r.has("certHash")&&(e.general.certHash=r.get("certHash")||""),r.has("left")&&e.armPairs[0]&&(e.armPairs[0].leftPath=r.get("left")||""),r.has("path")&&e.armPairs[0]&&!r.has("left")&&(e.armPairs[0].leftPath=r.get("path")||""),r.has("right")&&e.armPairs[0]&&(e.armPairs[0].rightPath=r.get("right")||""),r.has("depth")&&e.realsense[0]&&(e.realsense[0].path=r.get("depth")||""),r.has("depth2")&&e.realsense[1]&&(e.realsense[1].path=r.get("depth2")||"")}return e}function v(t){localStorage.setItem(x,JSON.stringify(t))}function W(t){const e=[];return t.armPairs.forEach((n,o)=>{const r=n.label||"Arm Pair "+(o+1),s=n.position||{x:0,y:0,z:0},d=n.rotation||{roll:0,pitch:0,yaw:0},c=n.enabled!==!1;e.push({id:n.id+"_left",label:r+" Left",path:n.leftPath||"",pairIdx:o,position:s,rotation:d,enabled:c}),e.push({id:n.id+"_right",label:r+" Right",path:n.rightPath||"",pairIdx:o,position:s,rotation:d,enabled:c})}),e}function f(t){return(t||"").replace(/"/g,""").replace(/</g,"<")}const $=["#ff6b6b","#ffa502","#2ed573","#1e90ff","#a855f7","#ff6348","#00d4ff","#eccc68"],V=[];function N(t){let e=0;for(let n=0;n<t.length;n++)e=(e<<5)-e+t.charCodeAt(n)|0;return $[Math.abs(e)%$.length]}function E(t,e,n){let o=t.querySelector(".tags-field");if(!o){o=document.createElement("div"),o.className="tags-field",o.innerHTML='<label>Tags</label><div class="tags-wrap"></div>';const d=t.querySelector(".field");d&&d.nextSibling?d.parentNode.insertBefore(o,d.nextSibling):t.appendChild(o)}t.dataset.tags=JSON.stringify(e);const r=o.querySelector(".tags-wrap");r.innerHTML="",e.forEach((d,c)=>{const u=document.createElement("span");u.className="tag-pill",u.style.background=N(d),u.textContent=d;const g=document.createElement("span");g.className="tag-x",g.textContent="×",g.addEventListener("click",()=>{e.splice(c,1),E(t,e,n),n()}),u.appendChild(g),r.appendChild(u)}),V.filter(d=>!e.includes(d)).forEach(d=>{const c=document.createElement("span");c.className="tag-pill tag-preset",c.style.color=N(d),c.textContent=d,c.addEventListener("click",()=>{e.push(d),E(t,e,n),n()}),r.appendChild(c)});const s=document.createElement("input");s.type="text",s.className="tag-input",s.placeholder="add...",s.addEventListener("keydown",d=>{if(d.key==="Enter"&&s.value.trim()){const c=s.value.trim().toLowerCase();e.includes(c)?s.value="":(e.push(c),E(t,e,n),n())}}),r.appendChild(s)}function ee(t,e,n={}){t.innerHTML=`
|
| 2 |
+
<div class="section">
|
| 3 |
+
<h2>General</h2>
|
| 4 |
+
<div class="field">
|
| 5 |
+
<label>Relay URL</label>
|
| 6 |
+
<input type="text" data-s="relay" />
|
| 7 |
+
</div>
|
| 8 |
+
<div class="field">
|
| 9 |
+
<label>Cert Hash</label>
|
| 10 |
+
<input type="text" data-s="certHash" placeholder="sha256 hex (optional)" />
|
| 11 |
+
</div>
|
| 12 |
+
</div>
|
| 13 |
+
<div class="section">
|
| 14 |
+
<h2>Arm Pairs</h2>
|
| 15 |
+
<div data-s="armPairsList"></div>
|
| 16 |
+
<button class="btn-add" data-s="addArmPair">+ Add Arm Pair</button>
|
| 17 |
+
</div>
|
| 18 |
+
<div class="section">
|
| 19 |
+
<h2>RealSense</h2>
|
| 20 |
+
<div data-s="realsenseList"></div>
|
| 21 |
+
<button class="btn-add" data-s="addRealSense">+ Add RealSense</button>
|
| 22 |
+
</div>
|
| 23 |
+
<div class="section">
|
| 24 |
+
<h2>Cameras</h2>
|
| 25 |
+
<div data-s="camerasList"></div>
|
| 26 |
+
<button class="btn-add" data-s="addCamera">+ Add Camera</button>
|
| 27 |
+
</div>
|
| 28 |
+
<div class="section">
|
| 29 |
+
<h2><input type="checkbox" data-s="audioEnabled" title="Enable/disable audio" /> Audio</h2>
|
| 30 |
+
<div class="field">
|
| 31 |
+
<label>Path</label>
|
| 32 |
+
<input type="text" data-s="audioPath" />
|
| 33 |
+
</div>
|
| 34 |
+
</div>
|
| 35 |
+
<div class="section">
|
| 36 |
+
<h2><input type="checkbox" data-s="chatEnabled" title="Enable/disable chat" /> Chat</h2>
|
| 37 |
+
<div class="field">
|
| 38 |
+
<label>Path</label>
|
| 39 |
+
<input type="text" data-s="chatPath" />
|
| 40 |
+
</div>
|
| 41 |
+
<!-- Username now set via HF OAuth or auto-generated -->
|
| 42 |
+
</div>
|
| 43 |
+
<div class="footer">
|
| 44 |
+
<button class="btn-primary" data-s="primaryBtn">${f(n.primaryButtonLabel||"Save")}</button>
|
| 45 |
+
<button class="btn-secondary" data-s="resetDefaults">Reset to Defaults</button>
|
| 46 |
+
<button class="btn-secondary" data-s="exportJson">Export JSON</button>
|
| 47 |
+
<button class="btn-secondary" data-s="importJson">Import JSON</button>
|
| 48 |
+
<button class="btn-secondary" data-s="importMachine">Import Machine</button>
|
| 49 |
+
<button class="btn-secondary" data-s="importClipboard">Paste Machine</button>
|
| 50 |
+
</div>
|
| 51 |
+
`;const o=document.createElement("input");o.type="file",o.accept=".json",o.style.display="none",t.appendChild(o);const r=document.createElement("input");r.type="file",r.accept=".json",r.style.display="none",t.appendChild(r);const s=a=>t.querySelector(`[data-s="${a}"]`);function d(){e.general.relay=s("relay").value,e.general.certHash=s("certHash").value,e.armPairs=[],s("armPairsList").querySelectorAll(".item-card").forEach(a=>{e.armPairs.push({id:a.dataset.id,enabled:a.querySelector(".pair-enabled").checked,label:a.querySelector(".pair-label").value,tags:JSON.parse(a.dataset.tags||"[]"),leftPath:a.querySelector(".pair-left").value,rightPath:a.querySelector(".pair-right").value,position:{x:parseFloat(a.querySelector(".pair-px").value)||0,y:parseFloat(a.querySelector(".pair-py").value)||0,z:parseFloat(a.querySelector(".pair-pz").value)||0},rotation:{roll:parseFloat(a.querySelector(".pair-rr").value)||0,pitch:parseFloat(a.querySelector(".pair-rp").value)||0,yaw:parseFloat(a.querySelector(".pair-ry").value)||0}})}),e.realsense=[],s("realsenseList").querySelectorAll(".item-card").forEach(a=>{e.realsense.push({id:a.dataset.id,enabled:a.querySelector(".rs-enabled").checked,label:a.querySelector(".rs-label").value,tags:JSON.parse(a.dataset.tags||"[]"),path:a.querySelector(".rs-path").value,position:{x:parseFloat(a.querySelector(".rs-px").value)||0,y:parseFloat(a.querySelector(".rs-py").value)||0,z:parseFloat(a.querySelector(".rs-pz").value)||0},rotation:{roll:parseFloat(a.querySelector(".rs-rr").value)||0,pitch:parseFloat(a.querySelector(".rs-rp").value)||0,yaw:parseFloat(a.querySelector(".rs-ry").value)||0},showColor:a.querySelector(".rs-showColor").checked,pointSize:parseFloat(a.querySelector(".rs-ptsize").value)||2})}),e.cameras=[],s("camerasList").querySelectorAll(".item-card").forEach(a=>{e.cameras.push({id:a.dataset.id,enabled:a.querySelector(".cam-enabled").checked,label:a.querySelector(".cam-label").value,tags:JSON.parse(a.dataset.tags||"[]"),path:a.querySelector(".cam-path").value})}),e.audio.enabled=s("audioEnabled").checked,e.audio.path=s("audioPath").value,e.chat.enabled=s("chatEnabled").checked,e.chat.path=s("chatPath").value}function c(a){d(),v(e),n.onSave&&n.onSave(e),a&&n.onLiveUpdate&&n.onLiveUpdate(a.type,a.index)}function u(){d(),v(e),n.onSave&&n.onSave(e),n.onStructuralChange&&n.onStructuralChange()}function g(a,i){const l=a.position||{x:0,y:0,z:0},m=a.rotation||{roll:0,pitch:0,yaw:0},p=document.createElement("div");return p.className="item-card",p.dataset.id=a.id,p.innerHTML=`
|
| 52 |
+
<div class="item-header">
|
| 53 |
+
<input type="checkbox" class="pair-enabled" ${a.enabled!==!1?"checked":""} title="Enable/disable this arm pair" />
|
| 54 |
+
<span>Arm Pair ${i+1}</span>
|
| 55 |
+
<button class="btn-remove" data-action="remove">Remove</button>
|
| 56 |
+
</div>
|
| 57 |
+
<div class="field">
|
| 58 |
+
<label>Label</label>
|
| 59 |
+
<input type="text" class="pair-label" value="${f(a.label)}" />
|
| 60 |
+
</div>
|
| 61 |
+
<div class="pair-row">
|
| 62 |
+
<label>Left Path</label>
|
| 63 |
+
<input type="text" class="pair-left" value="${f(a.leftPath)}" placeholder="e.g. anon/xoq-can-can0" />
|
| 64 |
+
</div>
|
| 65 |
+
<div class="pair-row">
|
| 66 |
+
<label>Right Path</label>
|
| 67 |
+
<input type="text" class="pair-right" value="${f(a.rightPath)}" placeholder="e.g. anon/xoq-can-can1" />
|
| 68 |
+
</div>
|
| 69 |
+
<div class="field">
|
| 70 |
+
<label>Position</label>
|
| 71 |
+
<div class="field-group">
|
| 72 |
+
<label>X</label><input type="number" class="pair-px" value="${l.x}" step="0.01" />
|
| 73 |
+
<label>Y</label><input type="number" class="pair-py" value="${l.y}" step="0.01" />
|
| 74 |
+
<label>Z</label><input type="number" class="pair-pz" value="${l.z}" step="0.01" />
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
<div class="field">
|
| 78 |
+
<label>Rotation</label>
|
| 79 |
+
<div class="field-group">
|
| 80 |
+
<label>Roll</label><input type="number" class="pair-rr" value="${m.roll}" step="1" />
|
| 81 |
+
<label>Pitch</label><input type="number" class="pair-rp" value="${m.pitch}" step="1" />
|
| 82 |
+
<label>Yaw</label><input type="number" class="pair-ry" value="${m.yaw}" step="1" />
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
`,E(p,a.tags||[],()=>c({type:"armPair",index:i})),p.querySelector('[data-action="remove"]').addEventListener("click",()=>{e.armPairs.length<=1||(p.remove(),u(),w())}),p.querySelectorAll(".pair-px, .pair-py, .pair-pz, .pair-rr, .pair-rp, .pair-ry").forEach(S=>{S.addEventListener("input",()=>c({type:"armPair",index:i}))}),p.querySelector(".pair-label").addEventListener("input",()=>c({type:"armPair",index:i})),p.querySelector(".pair-enabled").addEventListener("change",()=>u()),p.querySelectorAll(".pair-left, .pair-right").forEach(S=>{S.addEventListener("input",()=>u())}),p}function O(a,i){const l=document.createElement("div");return l.className="item-card",l.dataset.id=a.id,l.innerHTML=`
|
| 86 |
+
<div class="item-header">
|
| 87 |
+
<input type="checkbox" class="rs-enabled" ${a.enabled!==!1?"checked":""} title="Enable/disable this RealSense" />
|
| 88 |
+
<span>RealSense ${i+1}</span>
|
| 89 |
+
<button class="btn-remove" data-action="remove">Remove</button>
|
| 90 |
+
</div>
|
| 91 |
+
<div class="field">
|
| 92 |
+
<label>Label</label>
|
| 93 |
+
<input type="text" class="rs-label" value="${f(a.label)}" />
|
| 94 |
+
</div>
|
| 95 |
+
<div class="field">
|
| 96 |
+
<label>MoQ Path</label>
|
| 97 |
+
<input type="text" class="rs-path" value="${f(a.path)}" placeholder="e.g. anon/realsense" />
|
| 98 |
+
</div>
|
| 99 |
+
<div class="field">
|
| 100 |
+
<label>Position</label>
|
| 101 |
+
<div class="field-group">
|
| 102 |
+
<label>X</label><input type="number" class="rs-px" value="${a.position.x}" step="0.01" />
|
| 103 |
+
<label>Y</label><input type="number" class="rs-py" value="${a.position.y}" step="0.01" />
|
| 104 |
+
<label>Z</label><input type="number" class="rs-pz" value="${a.position.z}" step="0.01" />
|
| 105 |
+
</div>
|
| 106 |
+
</div>
|
| 107 |
+
<div class="field">
|
| 108 |
+
<label>Rotation</label>
|
| 109 |
+
<div class="field-group">
|
| 110 |
+
<label>Roll</label><input type="number" class="rs-rr" value="${a.rotation.roll}" step="1" />
|
| 111 |
+
<label>Pitch</label><input type="number" class="rs-rp" value="${a.rotation.pitch}" step="1" />
|
| 112 |
+
<label>Yaw</label><input type="number" class="rs-ry" value="${a.rotation.yaw}" step="1" />
|
| 113 |
+
</div>
|
| 114 |
+
</div>
|
| 115 |
+
<div class="field">
|
| 116 |
+
<label>Show Color</label>
|
| 117 |
+
<input type="checkbox" class="rs-showColor" ${a.showColor?"checked":""} />
|
| 118 |
+
</div>
|
| 119 |
+
<div class="field">
|
| 120 |
+
<label>Point Size</label>
|
| 121 |
+
<input type="number" class="rs-ptsize" value="${a.pointSize||2}" min="0.5" max="8" step="0.5" />
|
| 122 |
+
</div>
|
| 123 |
+
`,E(l,a.tags||[],()=>c({type:"realsense",index:i})),l.querySelector('[data-action="remove"]').addEventListener("click",()=>{e.realsense.length<=1||(l.remove(),u(),R())}),l.querySelectorAll(".rs-px, .rs-py, .rs-pz, .rs-rr, .rs-rp, .rs-ry").forEach(m=>{m.addEventListener("input",()=>c({type:"realsense",index:i}))}),l.querySelector(".rs-label").addEventListener("input",()=>c({type:"realsense",index:i})),l.querySelector(".rs-ptsize").addEventListener("input",()=>c({type:"realsense",index:i})),l.querySelector(".rs-enabled").addEventListener("change",()=>u()),l.querySelector(".rs-path").addEventListener("input",()=>u()),l.querySelector(".rs-showColor").addEventListener("change",()=>u()),l}function k(a,i){const l=document.createElement("div");return l.className="item-card",l.dataset.id=a.id,l.innerHTML=`
|
| 124 |
+
<div class="item-header">
|
| 125 |
+
<input type="checkbox" class="cam-enabled" ${a.enabled!==!1?"checked":""} title="Enable/disable this camera" />
|
| 126 |
+
<span>Camera ${i+1}</span>
|
| 127 |
+
<button class="btn-remove" data-action="remove">Remove</button>
|
| 128 |
+
</div>
|
| 129 |
+
<div class="field">
|
| 130 |
+
<label>Label</label>
|
| 131 |
+
<input type="text" class="cam-label" value="${f(a.label)}" />
|
| 132 |
+
</div>
|
| 133 |
+
<div class="field">
|
| 134 |
+
<label>MoQ Path</label>
|
| 135 |
+
<input type="text" class="cam-path" value="${f(a.path)}" placeholder="e.g. anon/webcam" />
|
| 136 |
+
</div>
|
| 137 |
+
`,E(l,a.tags||[],()=>c(null)),l.querySelector('[data-action="remove"]').addEventListener("click",()=>{l.remove(),u(),z()}),l.querySelector(".cam-enabled").addEventListener("change",()=>u()),l.querySelector(".cam-path").addEventListener("input",()=>u()),l.querySelector(".cam-label").addEventListener("input",()=>c(null)),l}function w(){const a=s("armPairsList");a.innerHTML="",e.armPairs.forEach((i,l)=>a.appendChild(g(i,l)))}function R(){const a=s("realsenseList");a.innerHTML="",e.realsense.forEach((i,l)=>a.appendChild(O(i,l)))}function z(){const a=s("camerasList");a.innerHTML="",e.cameras.forEach((i,l)=>a.appendChild(k(i,l)))}function T(){s("relay").value=e.general.relay,s("certHash").value=e.general.certHash}function L(){T(),w(),R(),z(),s("audioEnabled").checked=e.audio.enabled!==!1,s("audioPath").value=e.audio.path,s("chatEnabled").checked=e.chat.enabled!==!1,s("chatPath").value=e.chat.path,M()}function M(){[s("relay"),s("certHash"),s("audioPath"),s("chatPath")].forEach(l=>{l.removeEventListener("input",q),l.addEventListener("input",q)}),[s("audioEnabled"),s("chatEnabled")].forEach(l=>{l.removeEventListener("change",q),l.addEventListener("change",q)})}function q(){u()}s("addArmPair").addEventListener("click",()=>{const a=e.armPairs.length+1;e.armPairs.push({id:"pair"+a,enabled:!0,label:"Arm Pair "+a,leftPath:"",rightPath:"",position:{x:(a-1)*2,y:0,z:0},rotation:{roll:0,pitch:0,yaw:0}}),v(e),w(),n.onStructuralChange&&n.onStructuralChange()}),s("addRealSense").addEventListener("click",()=>{const a=e.realsense.length+1;e.realsense.push({id:"rs"+a,enabled:!0,label:"RealSense "+a,path:"",position:{x:-.33+(a-1),y:.84,z:.29+(a-1)},rotation:{roll:90,pitch:-222,yaw:0},showColor:!0,pointSize:2}),v(e),R(),n.onStructuralChange&&n.onStructuralChange()}),s("addCamera").addEventListener("click",()=>{const a=e.cameras.length+1;e.cameras.push({id:"cam"+a,enabled:!0,label:"Camera "+a,path:""}),v(e),z(),n.onStructuralChange&&n.onStructuralChange()}),s("primaryBtn").addEventListener("click",()=>{d(),v(e),n.onPrimaryAction&&n.onPrimaryAction()}),s("resetDefaults").addEventListener("click",()=>{confirm("Reset all settings to defaults?")&&(Object.assign(e,C()),v(e),L(),n.onStructuralChange&&n.onStructuralChange())}),s("exportJson").addEventListener("click",()=>{d(),v(e);const a=new Blob([JSON.stringify(e,null,2)],{type:"application/json"}),i=document.createElement("a");i.href=URL.createObjectURL(a),i.download="openarm-config.json",i.click()}),s("importJson").addEventListener("click",()=>o.click()),o.addEventListener("change",a=>{const i=a.target.files[0];if(!i)return;const l=new FileReader;l.onload=m=>{try{const p=JSON.parse(m.target.result);if(!p.general||!p.armPairs){alert("Invalid config file");return}Object.assign(e,p),v(e),L(),n.onStructuralChange&&n.onStructuralChange()}catch(p){alert("Failed to parse JSON: "+p.message)}},l.readAsText(i),a.target.value=""});function F(a){try{const i=JSON.parse(a);if(!i.services||!i.machine_id){alert("Not a valid machine.json (missing services or machine_id)");return}d();const l=i.hostname||i.machine_id;let m=[];const p=i.services.can||[];for(let b=0;b<p.length;b+=2){const h=p[b],y=p[b+1],P=e.armPairs.length+1;e.armPairs.push({id:"pair"+P,enabled:!0,label:l+" Arm "+P,leftPath:h?h.moq_path:"",rightPath:y?y.moq_path:"",position:{x:(P-1)*2,y:0,z:0},rotation:{roll:0,pitch:0,yaw:0}}),m.push("arm pair: "+(h?h.interface:"?")+" + "+(y?y.interface:"none"))}const S=i.services.fake_can||[];for(let b=0;b<S.length;b+=2){const h=S[b],y=S[b+1],P=e.armPairs.length+1;e.armPairs.push({id:"pair"+P,enabled:!0,label:l+" Fake Arm "+P,leftPath:h?h.moq_path:"",rightPath:y?y.moq_path:"",position:{x:(P-1)*2,y:0,z:0},rotation:{roll:0,pitch:0,yaw:0}}),m.push("fake arm pair: "+(h?h.interface:"?")+" + "+(y?y.interface:"none"))}const I=i.services.realsense||[];for(const b of I){const h=e.realsense.length+1;e.realsense.push({id:"rs"+h,enabled:!0,label:l+" RealSense "+h,path:b.moq_path,position:{x:-.33+(h-1),y:.84,z:.29+(h-1)},rotation:{roll:90,pitch:-45,yaw:0},showColor:!0,pointSize:2}),m.push("realsense: "+b.serial)}const J=i.services.cameras||[];for(const b of J){const h=e.cameras.length+1;e.cameras.push({id:"cam"+h,enabled:!0,label:l+" Camera "+h,path:b.moq_path}),m.push("camera: "+b.index)}if(m.length===0){alert("No services found in machine.json");return}v(e),L(),n.onStructuralChange&&n.onStructuralChange(),alert("Imported from "+l+`:
|
| 138 |
+
`+m.join(`
|
| 139 |
+
`))}catch(i){alert("Failed to parse machine.json: "+i.message)}}return s("importMachine").addEventListener("click",()=>r.click()),r.addEventListener("change",a=>{const i=a.target.files[0];if(!i)return;const l=new FileReader;l.onload=m=>F(m.target.result),l.readAsText(i),a.target.value=""}),s("importClipboard").addEventListener("click",async()=>{try{const a=await navigator.clipboard.readText();F(a)}catch(a){alert(`Could not read clipboard. Try copying the machine.json content first.
|
| 140 |
+
|
| 141 |
+
`+a.message)}}),L(),{renderAll:L,destroy(){t.innerHTML=""}}}export{W as f,ee as i,K as l};
|
assets/settings-CojHZpo_.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import"./modulepreload-polyfill-B5Qt9EMX.js";import{l as i,i as n}from"./openarm-settings-Cing9rdF.js";const t=i();n(document.getElementById("settingsContainer"),t,{primaryButtonLabel:"Save & Open Visualizer",onPrimaryAction(){window.location.href="openarm.html"}});
|
openarm.html
CHANGED
|
@@ -4,9 +4,9 @@
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<title>OpenArm 3D Visualizer - xoq</title>
|
| 7 |
-
<script type="module" crossorigin src="./assets/openarm-
|
| 8 |
<link rel="modulepreload" crossorigin href="./assets/modulepreload-polyfill-B5Qt9EMX.js">
|
| 9 |
-
<link rel="modulepreload" crossorigin href="./assets/openarm-settings-
|
| 10 |
<link rel="modulepreload" crossorigin href="./assets/connect-C3lO3qk6.js">
|
| 11 |
<link rel="modulepreload" crossorigin href="./assets/URDFLoader-BpNv9rVq.js">
|
| 12 |
<link rel="stylesheet" crossorigin href="./assets/openarm-settings-CsiNG4Tl.css">
|
|
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<title>OpenArm 3D Visualizer - xoq</title>
|
| 7 |
+
<script type="module" crossorigin src="./assets/openarm-BF3lx1-l.js"></script>
|
| 8 |
<link rel="modulepreload" crossorigin href="./assets/modulepreload-polyfill-B5Qt9EMX.js">
|
| 9 |
+
<link rel="modulepreload" crossorigin href="./assets/openarm-settings-Cing9rdF.js">
|
| 10 |
<link rel="modulepreload" crossorigin href="./assets/connect-C3lO3qk6.js">
|
| 11 |
<link rel="modulepreload" crossorigin href="./assets/URDFLoader-BpNv9rVq.js">
|
| 12 |
<link rel="stylesheet" crossorigin href="./assets/openarm-settings-CsiNG4Tl.css">
|
settings.html
CHANGED
|
@@ -4,9 +4,9 @@
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<title>OpenArm Settings - xoq</title>
|
| 7 |
-
<script type="module" crossorigin src="./assets/settings-
|
| 8 |
<link rel="modulepreload" crossorigin href="./assets/modulepreload-polyfill-B5Qt9EMX.js">
|
| 9 |
-
<link rel="modulepreload" crossorigin href="./assets/openarm-settings-
|
| 10 |
<link rel="stylesheet" crossorigin href="./assets/openarm-settings-CsiNG4Tl.css">
|
| 11 |
</head>
|
| 12 |
<body class="settings-page">
|
|
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<title>OpenArm Settings - xoq</title>
|
| 7 |
+
<script type="module" crossorigin src="./assets/settings-CojHZpo_.js"></script>
|
| 8 |
<link rel="modulepreload" crossorigin href="./assets/modulepreload-polyfill-B5Qt9EMX.js">
|
| 9 |
+
<link rel="modulepreload" crossorigin href="./assets/openarm-settings-Cing9rdF.js">
|
| 10 |
<link rel="stylesheet" crossorigin href="./assets/openarm-settings-CsiNG4Tl.css">
|
| 11 |
</head>
|
| 12 |
<body class="settings-page">
|