@limrun/ui 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.js +279 -258
- package/package.json +1 -1
- package/src/components/remote-control.tsx +34 -2
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});require('./index.css');const w=require("react/jsx-runtime"),y=require("react");function ne(E){var a,D,i="";if(typeof E=="string"||typeof E=="number")i+=E;else if(typeof E=="object")if(Array.isArray(E)){var l=E.length;for(a=0;a<l;a++)E[a]&&(D=ne(E[a]))&&(i&&(i+=" "),i+=D)}else for(D in E)E[D]&&(i&&(i+=" "),i+=D);return i}function _e(){for(var E,a,D=0,i="",l=arguments.length;D<l;D++)(E=arguments[D])&&(a=ne(E))&&(i&&(i+=" "),i+=a);return i}const j={INJECT_KEYCODE:0,INJECT_TOUCH_EVENT:2,SET_CLIPBOARD:9},b={ACTION_DOWN:0,ACTION_UP:1,ACTION_MOVE:2,ACTION_CANCEL:3,BUTTON_PRIMARY:1},e={ACTION_DOWN:0,ACTION_UP:1,META_NONE:0,META_SHIFT_ON:1,META_ALT_ON:2,META_CTRL_ON:4096,META_META_ON:65536,KEYCODE_0:7,KEYCODE_1:8,KEYCODE_2:9,KEYCODE_3:10,KEYCODE_4:11,KEYCODE_5:12,KEYCODE_6:13,KEYCODE_7:14,KEYCODE_8:15,KEYCODE_9:16,DPAD_UP:19,DPAD_DOWN:20,DPAD_LEFT:21,DPAD_RIGHT:22,KEYCODE_A:29,KEYCODE_B:30,KEYCODE_C:31,KEYCODE_D:32,KEYCODE_E:33,KEYCODE_F:34,KEYCODE_G:35,KEYCODE_H:36,KEYCODE_I:37,KEYCODE_J:38,KEYCODE_K:39,KEYCODE_L:40,KEYCODE_M:41,KEYCODE_N:42,KEYCODE_O:43,KEYCODE_P:44,KEYCODE_Q:45,KEYCODE_R:46,KEYCODE_S:47,KEYCODE_T:48,KEYCODE_U:49,KEYCODE_V:50,KEYCODE_W:51,KEYCODE_X:52,KEYCODE_Y:53,KEYCODE_Z:54,KEYCODE_COMMA:55,KEYCODE_PERIOD:56,KEYCODE_ALT_LEFT:57,KEYCODE_ALT_RIGHT:58,KEYCODE_SHIFT_LEFT:59,KEYCODE_SHIFT_RIGHT:60,KEYCODE_TAB:61,KEYCODE_SPACE:62,ENTER:66,DEL:67,KEYCODE_GRAVE:68,KEYCODE_MINUS:69,KEYCODE_EQUALS:70,KEYCODE_LEFT_BRACKET:71,KEYCODE_RIGHT_BRACKET:72,KEYCODE_BACKSLASH:73,KEYCODE_SEMICOLON:74,KEYCODE_APOSTROPHE:75,KEYCODE_SLASH:76,MENU:82,KEYCODE_PAGE_UP:92,KEYCODE_PAGE_DOWN:93,KEYCODE_ESCAPE:111,FORWARD_DEL:112,KEYCODE_CTRL_LEFT:113,KEYCODE_CTRL_RIGHT:114,KEYCODE_META_LEFT:117,KEYCODE_META_RIGHT:118,KEYCODE_MOVE_HOME:122,KEYCODE_MOVE_END:123,KEYCODE_INSERT:124,KEYCODE_F1:131,KEYCODE_F2:132,KEYCODE_F3:133,KEYCODE_F4:134,KEYCODE_F5:135,KEYCODE_F6:136,KEYCODE_F7:137,KEYCODE_F8:138,KEYCODE_F9:139,KEYCODE_F10:140,KEYCODE_F11:141,KEYCODE_F12:142,KEYCODE_NUMPAD_0:144,KEYCODE_NUMPAD_1:145,KEYCODE_NUMPAD_2:146,KEYCODE_NUMPAD_3:147,KEYCODE_NUMPAD_4:148,KEYCODE_NUMPAD_5:149,KEYCODE_NUMPAD_6:150,KEYCODE_NUMPAD_7:151,KEYCODE_NUMPAD_8:152,KEYCODE_NUMPAD_9:153,KEYCODE_NUMPAD_DIVIDE:154,KEYCODE_NUMPAD_MULTIPLY:155,KEYCODE_NUMPAD_SUBTRACT:156,KEYCODE_NUMPAD_ADD:157,KEYCODE_NUMPAD_DOT:158,KEYCODE_NUMPAD_COMMA:159,KEYCODE_NUMPAD_ENTER:160,KEYCODE_NUMPAD_EQUALS:161},re={KeyA:e.KEYCODE_A,KeyB:e.KEYCODE_B,KeyC:e.KEYCODE_C,KeyD:e.KEYCODE_D,KeyE:e.KEYCODE_E,KeyF:e.KEYCODE_F,KeyG:e.KEYCODE_G,KeyH:e.KEYCODE_H,KeyI:e.KEYCODE_I,KeyJ:e.KEYCODE_J,KeyK:e.KEYCODE_K,KeyL:e.KEYCODE_L,KeyM:e.KEYCODE_M,KeyN:e.KEYCODE_N,KeyO:e.KEYCODE_O,KeyP:e.KEYCODE_P,KeyQ:e.KEYCODE_Q,KeyR:e.KEYCODE_R,KeyS:e.KEYCODE_S,KeyT:e.KEYCODE_T,KeyU:e.KEYCODE_U,KeyV:e.KEYCODE_V,KeyW:e.KEYCODE_W,KeyX:e.KEYCODE_X,KeyY:e.KEYCODE_Y,KeyZ:e.KEYCODE_Z,Digit0:e.KEYCODE_0,Digit1:e.KEYCODE_1,Digit2:e.KEYCODE_2,Digit3:e.KEYCODE_3,Digit4:e.KEYCODE_4,Digit5:e.KEYCODE_5,Digit6:e.KEYCODE_6,Digit7:e.KEYCODE_7,Digit8:e.KEYCODE_8,Digit9:e.KEYCODE_9,Backquote:e.KEYCODE_GRAVE,Minus:e.KEYCODE_MINUS,Equal:e.KEYCODE_EQUALS,BracketLeft:e.KEYCODE_LEFT_BRACKET,BracketRight:e.KEYCODE_RIGHT_BRACKET,Backslash:e.KEYCODE_BACKSLASH,Semicolon:e.KEYCODE_SEMICOLON,Quote:e.KEYCODE_APOSTROPHE,Comma:e.KEYCODE_COMMA,Period:e.KEYCODE_PERIOD,Slash:e.KEYCODE_SLASH,Space:e.KEYCODE_SPACE,Tab:e.KEYCODE_TAB,Escape:e.KEYCODE_ESCAPE,ArrowUp:e.DPAD_UP,ArrowDown:e.DPAD_DOWN,ArrowLeft:e.DPAD_LEFT,ArrowRight:e.DPAD_RIGHT,Enter:e.ENTER,Backspace:e.DEL,Delete:e.FORWARD_DEL,Home:e.KEYCODE_MOVE_HOME,End:e.KEYCODE_MOVE_END,PageUp:e.KEYCODE_PAGE_UP,PageDown:e.KEYCODE_PAGE_DOWN,Insert:e.KEYCODE_INSERT,F1:e.KEYCODE_F1,F2:e.KEYCODE_F2,F3:e.KEYCODE_F3,F4:e.KEYCODE_F4,F5:e.KEYCODE_F5,F6:e.KEYCODE_F6,F7:e.KEYCODE_F7,F8:e.KEYCODE_F8,F9:e.KEYCODE_F9,F10:e.KEYCODE_F10,F11:e.KEYCODE_F11,F12:e.KEYCODE_F12,ShiftLeft:e.KEYCODE_SHIFT_LEFT,ShiftRight:e.KEYCODE_SHIFT_RIGHT,ControlLeft:e.KEYCODE_CTRL_LEFT,ControlRight:e.KEYCODE_CTRL_RIGHT,AltLeft:e.KEYCODE_ALT_LEFT,AltRight:e.KEYCODE_ALT_RIGHT,MetaLeft:e.KEYCODE_META_LEFT,MetaRight:e.KEYCODE_META_RIGHT,ContextMenu:e.MENU,Numpad0:e.KEYCODE_NUMPAD_0,Numpad1:e.KEYCODE_NUMPAD_1,Numpad2:e.KEYCODE_NUMPAD_2,Numpad3:e.KEYCODE_NUMPAD_3,Numpad4:e.KEYCODE_NUMPAD_4,Numpad5:e.KEYCODE_NUMPAD_5,Numpad6:e.KEYCODE_NUMPAD_6,Numpad7:e.KEYCODE_NUMPAD_7,Numpad8:e.KEYCODE_NUMPAD_8,Numpad9:e.KEYCODE_NUMPAD_9,NumpadDivide:e.KEYCODE_NUMPAD_DIVIDE,NumpadMultiply:e.KEYCODE_NUMPAD_MULTIPLY,NumpadSubtract:e.KEYCODE_NUMPAD_SUBTRACT,NumpadAdd:e.KEYCODE_NUMPAD_ADD,NumpadDecimal:e.KEYCODE_NUMPAD_DOT,NumpadComma:e.KEYCODE_NUMPAD_COMMA,NumpadEnter:e.KEYCODE_NUMPAD_ENTER,NumpadEqual:e.KEYCODE_NUMPAD_EQUALS};function De(E,a,D,i,l,Y,o=1,c=0,s=0){const u=new ArrayBuffer(32),N=new DataView(u);let d=0;return N.setUint8(d,j.INJECT_TOUCH_EVENT),d+=1,N.setUint8(d,E),d+=1,N.setBigInt64(d,BigInt(a)),d+=8,N.setInt32(d,Math.round(l),!0),d+=4,N.setInt32(d,Math.round(Y),!0),d+=4,N.setUint16(d,D,!0),d+=2,N.setUint16(d,i,!0),d+=2,N.setInt16(d,Math.round(o*65535),!0),d+=2,N.setInt32(d,c,!0),d+=4,N.setInt32(d,s,!0),u}function Ce(E,a=!0){const i=new TextEncoder().encode(E),l=new ArrayBuffer(14+i.length),Y=new DataView(l);let o=0;return Y.setUint8(o,j.SET_CLIPBOARD),o+=1,Y.setBigInt64(o,BigInt(0),!1),o+=8,Y.setUint8(o,a?1:0),o+=1,Y.setUint32(o,i.length,!1),o+=4,new Uint8Array(l,o).set(i),l}function W(E,a,D=0,i=0){const l=new ArrayBuffer(14),Y=new DataView(l);let o=0;return Y.setUint8(o,j.INJECT_KEYCODE),o+=1,Y.setUint8(o,E),o+=1,Y.setInt32(o,a,!0),o+=4,Y.setInt32(o,D,!0),o+=4,Y.setInt32(o,i,!0),l}const T=(...E)=>{window.debugRemoteControl&&console.log(...E)},f=(...E)=>{window.debugRemoteControl&&console.warn(...E)};function Oe(E){const a=E.code,D=re[a];if(!D)return f(`Unknown event.code: ${a}, key: ${E.key}`),null;let i=e.META_NONE;const l=a>="KeyA"&&a<="KeyZ",Y=E.getModifierState("CapsLock"),o=E.shiftKey;let c=o;return l&&(c=o!==Y),c&&(i|=e.META_SHIFT_ON),E.ctrlKey&&(i|=e.META_CTRL_ON),E.altKey&&(i|=e.META_ALT_ON),E.metaKey&&(i|=e.META_META_ON),{keycode:D,metaState:i}}const Ke=y.forwardRef(({className:E,url:a,token:D,sessionId:i,openUrl:l},Y)=>{const o=y.useRef(null),c=y.useRef(null),s=y.useRef(null),u=y.useRef(null),[N,d]=y.useState(!1),I=y.useRef(void 0),S=y.useRef(new Map),P=y.useRef(new Map),p=y.useRef(new Map),m=y.useMemo(()=>i||Math.random().toString(36).substring(2,15)+Math.random().toString(36).substring(2,15),[i]),C=r=>{T(r)},U=r=>{!u.current||u.current.readyState!=="open"||u.current.send(r)},M=r=>{if(r.preventDefault(),r.stopPropagation(),!u.current||u.current.readyState!=="open"||!o.current)return;const O=o.current,t=O.getBoundingClientRect(),n=O.videoWidth,_=O.videoHeight;if(!n||!_)return;const g=(A,K,k,h)=>{const F=t.width,v=t.height,$=n/_,se=F/v;let H=F,B=v;$>se?B=F/$:H=v*$;const ae=(F-H)/2,de=(v-B)/2,V=K-t.left-ae,G=k-t.top-de,x=V>=0&&V<=H&&G>=0&&G<=B;let J=0,q=0;x&&(J=Math.max(0,Math.min(n,V/H*n)),q=Math.max(0,Math.min(_,G/B*_)));let L=null,R=null,ue=1;const ee=b.BUTTON_PRIMARY;switch(h){case"down":x?(L=b.ACTION_DOWN,R={x:J,y:q},p.current.set(A,R),A===-1&&o.current?.focus()):p.current.delete(A);break;case"move":p.current.has(A)&&x&&(L=b.ACTION_MOVE,R={x:J,y:q},p.current.set(A,R));break;case"up":case"cancel":p.current.has(A)&&(L=h==="cancel"?b.ACTION_CANCEL:b.ACTION_UP,R=p.current.get(A),p.current.delete(A));break}if(L!==null&&R!==null){const te=De(L,A,n,_,R.x,R.y,ue,ee,ee);te&&U(te)}else(h==="up"||h==="cancel")&&p.current.delete(A)};if("touches"in r){const A=r.changedTouches;let K;switch(r.type){case"touchstart":K="down";break;case"touchmove":K="move";break;case"touchend":K="up";break;case"touchcancel":K="cancel";break;default:return}for(let k=0;k<A.length;k++){const h=A[k];g(h.identifier,h.clientX,h.clientY,K)}}else{let K=null;switch(r.type){case"mousedown":r.button===0&&(K="down");break;case"mousemove":p.current.has(-1)&&(K="move");break;case"mouseup":r.button===0&&(K="up");break;case"mouseleave":p.current.has(-1)&&(K="up");break}K&&g(-1,r.clientX,r.clientY,K)}},Q=r=>{if(r.preventDefault(),r.stopPropagation(),T("Keyboard event:",{type:r.type,key:r.key,keyCode:r.keyCode,code:r.code,target:r.target.tagName,focused:document.activeElement===o.current}),document.activeElement!==o.current){f("Video element not focused, skipping keyboard event");return}if(!u.current||u.current.readyState!=="open"){f("Data channel not ready for keyboard event:",u.current?.readyState);return}if(r.type==="keydown"){if(r.key.toLowerCase()==="v"&&(r.metaKey||r.ctrlKey)){T("Paste shortcut detected"),navigator.clipboard.readText().then(t=>{if(t){T("Pasting text via SET_CLIPBOARD:",t.substring(0,20)+(t.length>20?"...":""));const n=Ce(t,!0);U(n)}}).catch(t=>{console.error("Failed to read clipboard contents: ",t)});return}if(r.key.toLowerCase()==="m"&&(r.metaKey||r.ctrlKey)){T("Menu shortcut detected");const t=W(e.ACTION_DOWN,e.MENU,0,e.META_NONE);U(t);const n=W(e.ACTION_UP,e.MENU,0,e.META_NONE);U(n);return}}const O=Oe(r);if(O){const{keycode:t,metaState:n}=O,_=r.type==="keydown"?e.ACTION_DOWN:e.ACTION_UP;T(`Sending Keycode: key=${r.key}, code=${t}, action=${_}, meta=${n}`);const g=W(_,t,0,n);U(g)}else T(`Ignoring unhandled key event: type=${r.type}, key=${r.key}`)},oe=()=>{c.current&&c.current.readyState===WebSocket.OPEN&&c.current.send(JSON.stringify({type:"keepAlive",sessionId:m}))},X=()=>{I.current&&window.clearInterval(I.current),I.current=window.setInterval(oe,1e4)},Z=()=>{I.current&&(window.clearInterval(I.current),I.current=void 0)},z=()=>{document.hidden?Z():X()},ce=async()=>{try{c.current=new WebSocket(`${a}?token=${D}`),c.current.onerror=t=>{C("WebSocket error: "+t)},c.current.onclose=()=>{C("WebSocket closed")},await new Promise((t,n)=>{c.current&&(c.current.onopen=t,setTimeout(()=>n(new Error("WebSocket connection timeout")),5e3))});const O=await new Promise((t,n)=>{const _=setTimeout(()=>n(new Error("RTCConfiguration timeout")),5e3),g=A=>{try{const K=JSON.parse(A.data);K.type==="rtcConfiguration"&&(clearTimeout(_),c.current?.removeEventListener("message",g),t(K.rtcConfiguration))}catch(K){console.error("Error handling RTC configuration:",K),n(K)}};c.current?.addEventListener("message",g),c.current?.send(JSON.stringify({type:"requestRtcConfiguration",sessionId:m}))});if(s.current=new RTCPeerConnection(O),s.current.addTransceiver("audio",{direction:"recvonly"}),s.current.addTransceiver("video",{direction:"recvonly"}),u.current=s.current.createDataChannel("control",{ordered:!0,negotiated:!0,id:1}),u.current.onopen=()=>{if(C("Control channel opened"),c.current&&(c.current.send(JSON.stringify({type:"requestFrame",sessionId:m})),l))try{const t=decodeURIComponent(l);C("Opening URL"),c.current.send(JSON.stringify({type:"openUrl",url:t,sessionId:m}))}catch(t){console.error({error:t},"Error decoding URL, falling back to the original URL"),c.current.send(JSON.stringify({type:"openUrl",url:l,sessionId:m}))}},u.current.onclose=()=>{C("Control channel closed")},u.current.onerror=t=>{console.error("Control channel error:",t),C("Control channel error: "+t)},s.current.onconnectionstatechange=()=>{C("Connection state: "+s.current?.connectionState),d(s.current?.connectionState==="connected")},s.current.oniceconnectionstatechange=()=>{C("ICE state: "+s.current?.iceConnectionState)},s.current.ontrack=t=>{C("Received remote track: "+t.track.kind),t.track.kind==="video"&&o.current&&(T(`[${new Date().toISOString()}] Video track received:`,t.track),o.current.srcObject=t.streams[0])},s.current.onicecandidate=t=>{if(t.candidate&&c.current){const n={type:"candidate",candidate:t.candidate.candidate,sdpMid:t.candidate.sdpMid,sdpMLineIndex:t.candidate.sdpMLineIndex,sessionId:m};c.current.send(JSON.stringify(n)),C("Sent ICE candidate")}else C("ICE candidate gathering completed")},c.current.onmessage=async t=>{let n;try{n=JSON.parse(t.data)}catch(_){f("Error parsing message:",_);return}switch(C("Received: "+n.type),n.type){case"answer":if(!s.current){C("No peer connection, skipping answer");break}await s.current.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:n.sdp})),C("Set remote description");break;case"candidate":if(!s.current){C("No peer connection, skipping candidate");break}await s.current.addIceCandidate(new RTCIceCandidate({candidate:n.candidate,sdpMid:n.sdpMid,sdpMLineIndex:n.sdpMLineIndex})),C("Added ICE candidate");break;case"screenshot":if(typeof n.id!="string"||typeof n.dataUri!="string"){f("Received invalid screenshot success message:",n);break}const _=S.current.get(n.id);if(!_){f(`Received screenshot data for unknown or handled id: ${n.id}`);break}T(`Received screenshot data for id ${n.id}`),_({dataUri:n.dataUri}),S.current.delete(n.id),P.current.delete(n.id);break;case"screenshotError":if(typeof n.id!="string"||typeof n.message!="string"){f("Received invalid screenshot error message:",n);break}const g=P.current.get(n.id);if(!g){f(`Received screenshot error for unknown or handled id: ${n.id}`);break}f(`Received screenshot error for id ${n.id}: ${n.message}`),g(new Error(n.message)),S.current.delete(n.id),P.current.delete(n.id);break;default:f(`Received unhandled message type: ${n.type}`,n);break}},s.current){const t=await s.current.createOffer({offerToReceiveVideo:!0,offerToReceiveAudio:!1});await s.current.setLocalDescription(t),c.current&&c.current.send(JSON.stringify({type:"offer",sdp:t.sdp,sessionId:m})),C("Sent offer")}}catch(r){C("Error: "+r)}},Ee=()=>{c.current&&(c.current.close(),c.current=null),s.current&&(s.current.close(),s.current=null),o.current&&(o.current.srcObject=null),u.current&&(u.current.close(),u.current=null),d(!1),C("Stopped")};y.useEffect(()=>(ce(),document.hidden||X(),document.addEventListener("visibilitychange",z),()=>{Z(),Ee(),document.removeEventListener("visibilitychange",z)}),[a,D,i]);const ie=()=>{o.current&&o.current.focus()};return y.useImperativeHandle(Y,()=>({openUrl:r=>{if(!c.current||c.current.readyState!==WebSocket.OPEN){f("WebSocket not open, cannot send open_url command via ref.");return}try{const O=decodeURIComponent(r);C("Opening URL"),c.current.send(JSON.stringify({type:"openUrl",url:O,sessionId:m}))}catch(O){f("Error decoding or sending URL via ref:",{error:O,url:r}),c.current.send(JSON.stringify({type:"openUrl",url:r,sessionId:m}))}},sendKeyEvent:r=>{if(!u.current||u.current.readyState!=="open"){f("Data channel not ready for imperative key command:",u.current?.readyState);return}const O=re[r.code];if(!O){f(`Unknown event.code for imperative command: ${r.code}`);return}let t=e.META_NONE;r.shiftKey&&(t|=e.META_SHIFT_ON),r.altKey&&(t|=e.META_ALT_ON),r.ctrlKey&&(t|=e.META_CTRL_ON),r.metaKey&&(t|=e.META_META_ON);const n=r.type==="keydown"?e.ACTION_DOWN:e.ACTION_UP;T(`Sending Imperative Key Command: code=${r.code}, keycode=${O}, action=${n}, meta=${t}`);const _=W(n,O,0,t);_&&U(_)},screenshot:()=>new Promise((r,O)=>{if(!c.current||c.current.readyState!==WebSocket.OPEN)return f("WebSocket not open, cannot send screenshot command."),O(new Error("WebSocket is not connected or connection is not open."));const t=`ui-ss-${Date.now()}-${Math.random().toString(36).substring(2,9)}`,n={type:"screenshot",id:t};S.current.set(t,r),P.current.set(t,O),T("Sending screenshot request:",n);try{c.current.send(JSON.stringify(n))}catch(_){f("Failed to send screenshot request immediately:",_),S.current.delete(t),P.current.delete(t),O(_);return}setTimeout(()=>{S.current.has(t)&&(f(`Screenshot request timed out for id ${t}`),P.current.get(t)?.(new Error("Screenshot request timed out")),S.current.delete(t),P.current.delete(t))},3e4)})})),w.jsxs("div",{className:_e("rc-container",E),style:{touchAction:"none"},onMouseDown:M,onMouseMove:M,onMouseUp:M,onMouseLeave:M,onTouchStart:M,onTouchMove:M,onTouchEnd:M,onTouchCancel:M,children:[w.jsx("video",{ref:o,className:"rc-video",autoPlay:!0,playsInline:!0,muted:!0,tabIndex:0,style:{outline:"none",pointerEvents:"none"},onKeyDown:Q,onKeyUp:Q,onClick:ie,onFocus:()=>{o.current&&(o.current.style.outline="none")},onBlur:()=>{o.current&&(o.current.style.outline="none")}}),!N&&w.jsxs("div",{className:"rc-placeholder-wrapper",children:[w.jsx("div",{className:"rc-spinner"}),w.jsx("p",{className:"rc-placeholder-content",children:"Connecting..."})]})]})});exports.RemoteControl=Ke;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});require('./index.css');const b=require("react/jsx-runtime"),T=require("react");function ne(i){var u,D,s="";if(typeof i=="string"||typeof i=="number")s+=i;else if(typeof i=="object")if(Array.isArray(i)){var l=i.length;for(u=0;u<l;u++)i[u]&&(D=ne(i[u]))&&(s&&(s+=" "),s+=D)}else for(D in i)i[D]&&(s&&(s+=" "),s+=D);return s}function _e(){for(var i,u,D=0,s="",l=arguments.length;D<l;D++)(i=arguments[D])&&(u=ne(i))&&(s&&(s+=" "),s+=u);return s}const j={INJECT_KEYCODE:0,INJECT_TOUCH_EVENT:2,SET_CLIPBOARD:9},F={ACTION_DOWN:0,ACTION_UP:1,ACTION_MOVE:2,ACTION_CANCEL:3,BUTTON_PRIMARY:1},e={ACTION_DOWN:0,ACTION_UP:1,META_NONE:0,META_SHIFT_ON:1,META_ALT_ON:2,META_CTRL_ON:4096,META_META_ON:65536,KEYCODE_0:7,KEYCODE_1:8,KEYCODE_2:9,KEYCODE_3:10,KEYCODE_4:11,KEYCODE_5:12,KEYCODE_6:13,KEYCODE_7:14,KEYCODE_8:15,KEYCODE_9:16,DPAD_UP:19,DPAD_DOWN:20,DPAD_LEFT:21,DPAD_RIGHT:22,KEYCODE_A:29,KEYCODE_B:30,KEYCODE_C:31,KEYCODE_D:32,KEYCODE_E:33,KEYCODE_F:34,KEYCODE_G:35,KEYCODE_H:36,KEYCODE_I:37,KEYCODE_J:38,KEYCODE_K:39,KEYCODE_L:40,KEYCODE_M:41,KEYCODE_N:42,KEYCODE_O:43,KEYCODE_P:44,KEYCODE_Q:45,KEYCODE_R:46,KEYCODE_S:47,KEYCODE_T:48,KEYCODE_U:49,KEYCODE_V:50,KEYCODE_W:51,KEYCODE_X:52,KEYCODE_Y:53,KEYCODE_Z:54,KEYCODE_COMMA:55,KEYCODE_PERIOD:56,KEYCODE_ALT_LEFT:57,KEYCODE_ALT_RIGHT:58,KEYCODE_SHIFT_LEFT:59,KEYCODE_SHIFT_RIGHT:60,KEYCODE_TAB:61,KEYCODE_SPACE:62,ENTER:66,DEL:67,KEYCODE_GRAVE:68,KEYCODE_MINUS:69,KEYCODE_EQUALS:70,KEYCODE_LEFT_BRACKET:71,KEYCODE_RIGHT_BRACKET:72,KEYCODE_BACKSLASH:73,KEYCODE_SEMICOLON:74,KEYCODE_APOSTROPHE:75,KEYCODE_SLASH:76,MENU:82,KEYCODE_PAGE_UP:92,KEYCODE_PAGE_DOWN:93,KEYCODE_ESCAPE:111,FORWARD_DEL:112,KEYCODE_CTRL_LEFT:113,KEYCODE_CTRL_RIGHT:114,KEYCODE_META_LEFT:117,KEYCODE_META_RIGHT:118,KEYCODE_MOVE_HOME:122,KEYCODE_MOVE_END:123,KEYCODE_INSERT:124,KEYCODE_F1:131,KEYCODE_F2:132,KEYCODE_F3:133,KEYCODE_F4:134,KEYCODE_F5:135,KEYCODE_F6:136,KEYCODE_F7:137,KEYCODE_F8:138,KEYCODE_F9:139,KEYCODE_F10:140,KEYCODE_F11:141,KEYCODE_F12:142,KEYCODE_NUMPAD_0:144,KEYCODE_NUMPAD_1:145,KEYCODE_NUMPAD_2:146,KEYCODE_NUMPAD_3:147,KEYCODE_NUMPAD_4:148,KEYCODE_NUMPAD_5:149,KEYCODE_NUMPAD_6:150,KEYCODE_NUMPAD_7:151,KEYCODE_NUMPAD_8:152,KEYCODE_NUMPAD_9:153,KEYCODE_NUMPAD_DIVIDE:154,KEYCODE_NUMPAD_MULTIPLY:155,KEYCODE_NUMPAD_SUBTRACT:156,KEYCODE_NUMPAD_ADD:157,KEYCODE_NUMPAD_DOT:158,KEYCODE_NUMPAD_COMMA:159,KEYCODE_NUMPAD_ENTER:160,KEYCODE_NUMPAD_EQUALS:161},re={KeyA:e.KEYCODE_A,KeyB:e.KEYCODE_B,KeyC:e.KEYCODE_C,KeyD:e.KEYCODE_D,KeyE:e.KEYCODE_E,KeyF:e.KEYCODE_F,KeyG:e.KEYCODE_G,KeyH:e.KEYCODE_H,KeyI:e.KEYCODE_I,KeyJ:e.KEYCODE_J,KeyK:e.KEYCODE_K,KeyL:e.KEYCODE_L,KeyM:e.KEYCODE_M,KeyN:e.KEYCODE_N,KeyO:e.KEYCODE_O,KeyP:e.KEYCODE_P,KeyQ:e.KEYCODE_Q,KeyR:e.KEYCODE_R,KeyS:e.KEYCODE_S,KeyT:e.KEYCODE_T,KeyU:e.KEYCODE_U,KeyV:e.KEYCODE_V,KeyW:e.KEYCODE_W,KeyX:e.KEYCODE_X,KeyY:e.KEYCODE_Y,KeyZ:e.KEYCODE_Z,Digit0:e.KEYCODE_0,Digit1:e.KEYCODE_1,Digit2:e.KEYCODE_2,Digit3:e.KEYCODE_3,Digit4:e.KEYCODE_4,Digit5:e.KEYCODE_5,Digit6:e.KEYCODE_6,Digit7:e.KEYCODE_7,Digit8:e.KEYCODE_8,Digit9:e.KEYCODE_9,Backquote:e.KEYCODE_GRAVE,Minus:e.KEYCODE_MINUS,Equal:e.KEYCODE_EQUALS,BracketLeft:e.KEYCODE_LEFT_BRACKET,BracketRight:e.KEYCODE_RIGHT_BRACKET,Backslash:e.KEYCODE_BACKSLASH,Semicolon:e.KEYCODE_SEMICOLON,Quote:e.KEYCODE_APOSTROPHE,Comma:e.KEYCODE_COMMA,Period:e.KEYCODE_PERIOD,Slash:e.KEYCODE_SLASH,Space:e.KEYCODE_SPACE,Tab:e.KEYCODE_TAB,Escape:e.KEYCODE_ESCAPE,ArrowUp:e.DPAD_UP,ArrowDown:e.DPAD_DOWN,ArrowLeft:e.DPAD_LEFT,ArrowRight:e.DPAD_RIGHT,Enter:e.ENTER,Backspace:e.DEL,Delete:e.FORWARD_DEL,Home:e.KEYCODE_MOVE_HOME,End:e.KEYCODE_MOVE_END,PageUp:e.KEYCODE_PAGE_UP,PageDown:e.KEYCODE_PAGE_DOWN,Insert:e.KEYCODE_INSERT,F1:e.KEYCODE_F1,F2:e.KEYCODE_F2,F3:e.KEYCODE_F3,F4:e.KEYCODE_F4,F5:e.KEYCODE_F5,F6:e.KEYCODE_F6,F7:e.KEYCODE_F7,F8:e.KEYCODE_F8,F9:e.KEYCODE_F9,F10:e.KEYCODE_F10,F11:e.KEYCODE_F11,F12:e.KEYCODE_F12,ShiftLeft:e.KEYCODE_SHIFT_LEFT,ShiftRight:e.KEYCODE_SHIFT_RIGHT,ControlLeft:e.KEYCODE_CTRL_LEFT,ControlRight:e.KEYCODE_CTRL_RIGHT,AltLeft:e.KEYCODE_ALT_LEFT,AltRight:e.KEYCODE_ALT_RIGHT,MetaLeft:e.KEYCODE_META_LEFT,MetaRight:e.KEYCODE_META_RIGHT,ContextMenu:e.MENU,Numpad0:e.KEYCODE_NUMPAD_0,Numpad1:e.KEYCODE_NUMPAD_1,Numpad2:e.KEYCODE_NUMPAD_2,Numpad3:e.KEYCODE_NUMPAD_3,Numpad4:e.KEYCODE_NUMPAD_4,Numpad5:e.KEYCODE_NUMPAD_5,Numpad6:e.KEYCODE_NUMPAD_6,Numpad7:e.KEYCODE_NUMPAD_7,Numpad8:e.KEYCODE_NUMPAD_8,Numpad9:e.KEYCODE_NUMPAD_9,NumpadDivide:e.KEYCODE_NUMPAD_DIVIDE,NumpadMultiply:e.KEYCODE_NUMPAD_MULTIPLY,NumpadSubtract:e.KEYCODE_NUMPAD_SUBTRACT,NumpadAdd:e.KEYCODE_NUMPAD_ADD,NumpadDecimal:e.KEYCODE_NUMPAD_DOT,NumpadComma:e.KEYCODE_NUMPAD_COMMA,NumpadEnter:e.KEYCODE_NUMPAD_ENTER,NumpadEqual:e.KEYCODE_NUMPAD_EQUALS};function Ce(i,u,D,s,l,A,o=1,c=0,d=0){const C=new ArrayBuffer(32),N=new DataView(C);let _=0;return N.setUint8(_,j.INJECT_TOUCH_EVENT),_+=1,N.setUint8(_,i),_+=1,N.setBigInt64(_,BigInt(u)),_+=8,N.setInt32(_,Math.round(l),!0),_+=4,N.setInt32(_,Math.round(A),!0),_+=4,N.setUint16(_,D,!0),_+=2,N.setUint16(_,s,!0),_+=2,N.setInt16(_,Math.round(o*65535),!0),_+=2,N.setInt32(_,c,!0),_+=4,N.setInt32(_,d,!0),C}function De(i,u=!0){const s=new TextEncoder().encode(i),l=new ArrayBuffer(14+s.length),A=new DataView(l);let o=0;return A.setUint8(o,j.SET_CLIPBOARD),o+=1,A.setBigInt64(o,BigInt(0),!1),o+=8,A.setUint8(o,u?1:0),o+=1,A.setUint32(o,s.length,!1),o+=4,new Uint8Array(l,o).set(s),l}function W(i,u,D=0,s=0){const l=new ArrayBuffer(14),A=new DataView(l);let o=0;return A.setUint8(o,j.INJECT_KEYCODE),o+=1,A.setUint8(o,i),o+=1,A.setInt32(o,u,!0),o+=4,A.setInt32(o,D,!0),o+=4,A.setInt32(o,s,!0),l}const g=(...i)=>{window.debugRemoteControl&&console.log(...i)},Y=(...i)=>{window.debugRemoteControl&&console.warn(...i)};function Oe(i){const u=i.code,D=re[u];if(!D)return Y(`Unknown event.code: ${u}, key: ${i.key}`),null;let s=e.META_NONE;const l=u>="KeyA"&&u<="KeyZ",A=i.getModifierState("CapsLock"),o=i.shiftKey;let c=o;return l&&(c=o!==A),c&&(s|=e.META_SHIFT_ON),i.ctrlKey&&(s|=e.META_CTRL_ON),i.altKey&&(s|=e.META_ALT_ON),i.metaKey&&(s|=e.META_META_ON),{keycode:D,metaState:s}}const Ke=T.forwardRef(({className:i,url:u,token:D,sessionId:s,openUrl:l},A)=>{const o=T.useRef(null),c=T.useRef(null),d=T.useRef(null),C=T.useRef(null),[N,_]=T.useState(!1),k=T.useRef(void 0),I=T.useRef(new Map),U=T.useRef(new Map),m=T.useRef(new Map),R=T.useMemo(()=>s||Math.random().toString(36).substring(2,15)+Math.random().toString(36).substring(2,15),[s]),O=n=>{g(n)},L=n=>{!C.current||C.current.readyState!=="open"||C.current.send(n)},S=n=>{if(n.preventDefault(),n.stopPropagation(),!C.current||C.current.readyState!=="open"||!o.current)return;const K=o.current,E=K.getBoundingClientRect(),t=K.videoWidth,r=K.videoHeight;if(!t||!r)return;const y=(a,f,p,M)=>{const h=E.width,v=E.height,$=t/r,se=h/v;let H=h,B=v;$>se?B=h/$:H=v*$;const ae=(h-H)/2,de=(v-B)/2,V=f-E.left-ae,G=p-E.top-de,x=V>=0&&V<=H&&G>=0&&G<=B;let J=0,q=0;x&&(J=Math.max(0,Math.min(t,V/H*t)),q=Math.max(0,Math.min(r,G/B*r)));let w=null,P=null,ue=1;const ee=F.BUTTON_PRIMARY;switch(M){case"down":x?(w=F.ACTION_DOWN,P={x:J,y:q},m.current.set(a,P),a===-1&&o.current?.focus()):m.current.delete(a);break;case"move":m.current.has(a)&&x&&(w=F.ACTION_MOVE,P={x:J,y:q},m.current.set(a,P));break;case"up":case"cancel":m.current.has(a)&&(w=M==="cancel"?F.ACTION_CANCEL:F.ACTION_UP,P=m.current.get(a),m.current.delete(a));break}if(w!==null&&P!==null){const te=Ce(w,a,t,r,P.x,P.y,ue,ee,ee);te&&L(te)}else(M==="up"||M==="cancel")&&m.current.delete(a)};if("touches"in n){const a=n.changedTouches;let f;switch(n.type){case"touchstart":f="down";break;case"touchmove":f="move";break;case"touchend":f="up";break;case"touchcancel":f="cancel";break;default:return}for(let p=0;p<a.length;p++){const M=a[p];y(M.identifier,M.clientX,M.clientY,f)}}else{let f=null;switch(n.type){case"mousedown":n.button===0&&(f="down");break;case"mousemove":m.current.has(-1)&&(f="move");break;case"mouseup":n.button===0&&(f="up");break;case"mouseleave":m.current.has(-1)&&(f="up");break}f&&y(-1,n.clientX,n.clientY,f)}},Q=n=>{if(n.preventDefault(),n.stopPropagation(),g("Keyboard event:",{type:n.type,key:n.key,keyCode:n.keyCode,code:n.code,target:n.target.tagName,focused:document.activeElement===o.current}),document.activeElement!==o.current){Y("Video element not focused, skipping keyboard event");return}if(!C.current||C.current.readyState!=="open"){Y("Data channel not ready for keyboard event:",C.current?.readyState);return}if(n.type==="keydown"){if(n.key.toLowerCase()==="v"&&(n.metaKey||n.ctrlKey)){g("Paste shortcut detected"),navigator.clipboard.readText().then(E=>{if(E){g("Pasting text via SET_CLIPBOARD:",E.substring(0,20)+(E.length>20?"...":""));const t=De(E,!0);L(t)}}).catch(E=>{console.error("Failed to read clipboard contents: ",E)});return}if(n.key.toLowerCase()==="m"&&(n.metaKey||n.ctrlKey)){g("Menu shortcut detected");const E=W(e.ACTION_DOWN,e.MENU,0,e.META_NONE);L(E);const t=W(e.ACTION_UP,e.MENU,0,e.META_NONE);L(t);return}}const K=Oe(n);if(K){const{keycode:E,metaState:t}=K,r=n.type==="keydown"?e.ACTION_DOWN:e.ACTION_UP;g(`Sending Keycode: key=${n.key}, code=${E}, action=${r}, meta=${t}`);const y=W(r,E,0,t);L(y)}else g(`Ignoring unhandled key event: type=${n.type}, key=${n.key}`)},oe=()=>{c.current&&c.current.readyState===WebSocket.OPEN&&c.current.send(JSON.stringify({type:"keepAlive",sessionId:R}))},X=()=>{k.current&&window.clearInterval(k.current),k.current=window.setInterval(oe,1e4)},Z=()=>{k.current&&(window.clearInterval(k.current),k.current=void 0)},z=()=>{document.hidden?Z():X()},ce=async()=>{try{c.current=new WebSocket(`${u}?token=${D}`),c.current.onerror=t=>{O("WebSocket error: "+t)},c.current.onclose=()=>{O("WebSocket closed")},await new Promise((t,r)=>{c.current&&(c.current.onopen=t,setTimeout(()=>r(new Error("WebSocket connection timeout")),5e3))});const K=await new Promise((t,r)=>{const y=setTimeout(()=>r(new Error("RTCConfiguration timeout")),5e3),a=f=>{try{const p=JSON.parse(f.data);p.type==="rtcConfiguration"&&(clearTimeout(y),c.current?.removeEventListener("message",a),t(p.rtcConfiguration))}catch(p){console.error("Error handling RTC configuration:",p),r(p)}};c.current?.addEventListener("message",a),c.current?.send(JSON.stringify({type:"requestRtcConfiguration",sessionId:R}))});d.current=new RTCPeerConnection(K),d.current.addTransceiver("audio",{direction:"recvonly"});const E=d.current.addTransceiver("video",{direction:"recvonly"});if(RTCRtpReceiver.getCapabilities){const t=RTCRtpReceiver.getCapabilities("video");if(t&&t.codecs){const y=t.codecs.sort((a,f)=>{const p=M=>{const h=M.mimeType.toLowerCase();return h.includes("vp9")?1:h.includes("h265")||h.includes("hevc")?2:h.includes("h264")||h.includes("avc")?3:4};return p(a)-p(f)});E.setCodecPreferences(y),g("Set codec preferences:",y.map(a=>a.mimeType).join(", "))}}if(C.current=d.current.createDataChannel("control",{ordered:!0,negotiated:!0,id:1}),C.current.onopen=()=>{if(O("Control channel opened"),c.current){for(let t=0;t<12;t++)setTimeout(()=>{c.current&&c.current.send(JSON.stringify({type:"requestFrame",sessionId:R}))},t*125);if(l)try{const t=decodeURIComponent(l);O("Opening URL"),c.current.send(JSON.stringify({type:"openUrl",url:t,sessionId:R}))}catch(t){console.error({error:t},"Error decoding URL, falling back to the original URL"),c.current.send(JSON.stringify({type:"openUrl",url:l,sessionId:R}))}}},C.current.onclose=()=>{O("Control channel closed")},C.current.onerror=t=>{console.error("Control channel error:",t),O("Control channel error: "+t)},d.current.onconnectionstatechange=()=>{O("Connection state: "+d.current?.connectionState),_(d.current?.connectionState==="connected")},d.current.oniceconnectionstatechange=()=>{O("ICE state: "+d.current?.iceConnectionState)},d.current.ontrack=t=>{O("Received remote track: "+t.track.kind),t.track.kind==="video"&&o.current&&(g(`[${new Date().toISOString()}] Video track received:`,t.track),o.current.srcObject=t.streams[0])},d.current.onicecandidate=t=>{if(t.candidate&&c.current){const r={type:"candidate",candidate:t.candidate.candidate,sdpMid:t.candidate.sdpMid,sdpMLineIndex:t.candidate.sdpMLineIndex,sessionId:R};c.current.send(JSON.stringify(r)),O("Sent ICE candidate")}else O("ICE candidate gathering completed")},c.current.onmessage=async t=>{let r;try{r=JSON.parse(t.data)}catch(y){Y("Error parsing message:",y);return}switch(O("Received: "+r.type),r.type){case"answer":if(!d.current){O("No peer connection, skipping answer");break}await d.current.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:r.sdp})),O("Set remote description");break;case"candidate":if(!d.current){O("No peer connection, skipping candidate");break}await d.current.addIceCandidate(new RTCIceCandidate({candidate:r.candidate,sdpMid:r.sdpMid,sdpMLineIndex:r.sdpMLineIndex})),O("Added ICE candidate");break;case"screenshot":if(typeof r.id!="string"||typeof r.dataUri!="string"){Y("Received invalid screenshot success message:",r);break}const y=I.current.get(r.id);if(!y){Y(`Received screenshot data for unknown or handled id: ${r.id}`);break}g(`Received screenshot data for id ${r.id}`),y({dataUri:r.dataUri}),I.current.delete(r.id),U.current.delete(r.id);break;case"screenshotError":if(typeof r.id!="string"||typeof r.message!="string"){Y("Received invalid screenshot error message:",r);break}const a=U.current.get(r.id);if(!a){Y(`Received screenshot error for unknown or handled id: ${r.id}`);break}Y(`Received screenshot error for id ${r.id}: ${r.message}`),a(new Error(r.message)),I.current.delete(r.id),U.current.delete(r.id);break;default:Y(`Received unhandled message type: ${r.type}`,r);break}},d.current){const t=await d.current.createOffer({offerToReceiveVideo:!0,offerToReceiveAudio:!1});await d.current.setLocalDescription(t),c.current&&c.current.send(JSON.stringify({type:"offer",sdp:t.sdp,sessionId:R})),O("Sent offer")}}catch(n){O("Error: "+n)}},Ee=()=>{c.current&&(c.current.close(),c.current=null),d.current&&(d.current.close(),d.current=null),o.current&&(o.current.srcObject=null),C.current&&(C.current.close(),C.current=null),_(!1),O("Stopped")};T.useEffect(()=>(ce(),document.hidden||X(),document.addEventListener("visibilitychange",z),()=>{Z(),Ee(),document.removeEventListener("visibilitychange",z)}),[u,D,s]);const ie=()=>{o.current&&o.current.focus()};return T.useImperativeHandle(A,()=>({openUrl:n=>{if(!c.current||c.current.readyState!==WebSocket.OPEN){Y("WebSocket not open, cannot send open_url command via ref.");return}try{const K=decodeURIComponent(n);O("Opening URL"),c.current.send(JSON.stringify({type:"openUrl",url:K,sessionId:R}))}catch(K){Y("Error decoding or sending URL via ref:",{error:K,url:n}),c.current.send(JSON.stringify({type:"openUrl",url:n,sessionId:R}))}},sendKeyEvent:n=>{if(!C.current||C.current.readyState!=="open"){Y("Data channel not ready for imperative key command:",C.current?.readyState);return}const K=re[n.code];if(!K){Y(`Unknown event.code for imperative command: ${n.code}`);return}let E=e.META_NONE;n.shiftKey&&(E|=e.META_SHIFT_ON),n.altKey&&(E|=e.META_ALT_ON),n.ctrlKey&&(E|=e.META_CTRL_ON),n.metaKey&&(E|=e.META_META_ON);const t=n.type==="keydown"?e.ACTION_DOWN:e.ACTION_UP;g(`Sending Imperative Key Command: code=${n.code}, keycode=${K}, action=${t}, meta=${E}`);const r=W(t,K,0,E);r&&L(r)},screenshot:()=>new Promise((n,K)=>{if(!c.current||c.current.readyState!==WebSocket.OPEN)return Y("WebSocket not open, cannot send screenshot command."),K(new Error("WebSocket is not connected or connection is not open."));const E=`ui-ss-${Date.now()}-${Math.random().toString(36).substring(2,9)}`,t={type:"screenshot",id:E};I.current.set(E,n),U.current.set(E,K),g("Sending screenshot request:",t);try{c.current.send(JSON.stringify(t))}catch(r){Y("Failed to send screenshot request immediately:",r),I.current.delete(E),U.current.delete(E),K(r);return}setTimeout(()=>{I.current.has(E)&&(Y(`Screenshot request timed out for id ${E}`),U.current.get(E)?.(new Error("Screenshot request timed out")),I.current.delete(E),U.current.delete(E))},3e4)})})),b.jsxs("div",{className:_e("rc-container",i),style:{touchAction:"none"},onMouseDown:S,onMouseMove:S,onMouseUp:S,onMouseLeave:S,onTouchStart:S,onTouchMove:S,onTouchEnd:S,onTouchCancel:S,children:[b.jsx("video",{ref:o,className:"rc-video",autoPlay:!0,playsInline:!0,muted:!0,tabIndex:0,style:{outline:"none",pointerEvents:"none"},onKeyDown:Q,onKeyUp:Q,onClick:ie,onFocus:()=>{o.current&&(o.current.style.outline="none")},onBlur:()=>{o.current&&(o.current.style.outline="none")}}),!N&&b.jsxs("div",{className:"rc-placeholder-wrapper",children:[b.jsx("div",{className:"rc-spinner"}),b.jsx("p",{className:"rc-placeholder-content",children:"Connecting..."})]})]})});exports.RemoteControl=Ke;
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { jsxs as ne, jsx as q } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef as
|
|
3
|
-
import './index.css';function re(
|
|
4
|
-
var
|
|
5
|
-
if (typeof
|
|
6
|
-
else if (typeof
|
|
7
|
-
var l =
|
|
8
|
-
for (
|
|
9
|
-
} else for (D in
|
|
10
|
-
return
|
|
2
|
+
import { forwardRef as Ce, useRef as P, useState as De, useMemo as Oe, useEffect as Ke, useImperativeHandle as le } from "react";
|
|
3
|
+
import './index.css';function re(i) {
|
|
4
|
+
var u, D, s = "";
|
|
5
|
+
if (typeof i == "string" || typeof i == "number") s += i;
|
|
6
|
+
else if (typeof i == "object") if (Array.isArray(i)) {
|
|
7
|
+
var l = i.length;
|
|
8
|
+
for (u = 0; u < l; u++) i[u] && (D = re(i[u])) && (s && (s += " "), s += D);
|
|
9
|
+
} else for (D in i) i[D] && (s && (s += " "), s += D);
|
|
10
|
+
return s;
|
|
11
11
|
}
|
|
12
|
-
function
|
|
13
|
-
for (var
|
|
14
|
-
return
|
|
12
|
+
function fe() {
|
|
13
|
+
for (var i, u, D = 0, s = "", l = arguments.length; D < l; D++) (i = arguments[D]) && (u = re(i)) && (s && (s += " "), s += u);
|
|
14
|
+
return s;
|
|
15
15
|
}
|
|
16
16
|
const Q = {
|
|
17
17
|
INJECT_KEYCODE: 0,
|
|
18
18
|
INJECT_TOUCH_EVENT: 2,
|
|
19
19
|
SET_CLIPBOARD: 9
|
|
20
|
-
},
|
|
20
|
+
}, b = {
|
|
21
21
|
ACTION_DOWN: 0,
|
|
22
22
|
ACTION_UP: 1,
|
|
23
23
|
ACTION_MOVE: 2,
|
|
@@ -256,449 +256,470 @@ const Q = {
|
|
|
256
256
|
NumpadEnter: e.KEYCODE_NUMPAD_ENTER,
|
|
257
257
|
NumpadEqual: e.KEYCODE_NUMPAD_EQUALS
|
|
258
258
|
};
|
|
259
|
-
function
|
|
260
|
-
const
|
|
261
|
-
let
|
|
262
|
-
return
|
|
259
|
+
function Ye(i, u, D, s, l, A, o = 1, c = 0, d = 0) {
|
|
260
|
+
const C = new ArrayBuffer(32), N = new DataView(C);
|
|
261
|
+
let _ = 0;
|
|
262
|
+
return N.setUint8(_, Q.INJECT_TOUCH_EVENT), _ += 1, N.setUint8(_, i), _ += 1, N.setBigInt64(_, BigInt(u)), _ += 8, N.setInt32(_, Math.round(l), !0), _ += 4, N.setInt32(_, Math.round(A), !0), _ += 4, N.setUint16(_, D, !0), _ += 2, N.setUint16(_, s, !0), _ += 2, N.setInt16(_, Math.round(o * 65535), !0), _ += 2, N.setInt32(_, c, !0), _ += 4, N.setInt32(_, d, !0), C;
|
|
263
263
|
}
|
|
264
|
-
function Ae(
|
|
265
|
-
const
|
|
264
|
+
function Ae(i, u = !0) {
|
|
265
|
+
const s = new TextEncoder().encode(i), l = new ArrayBuffer(14 + s.length), A = new DataView(l);
|
|
266
266
|
let o = 0;
|
|
267
|
-
return
|
|
267
|
+
return A.setUint8(o, Q.SET_CLIPBOARD), o += 1, A.setBigInt64(o, BigInt(0), !1), o += 8, A.setUint8(o, u ? 1 : 0), o += 1, A.setUint32(o, s.length, !1), o += 4, new Uint8Array(l, o).set(s), l;
|
|
268
268
|
}
|
|
269
|
-
function B(
|
|
270
|
-
const l = new ArrayBuffer(14),
|
|
269
|
+
function B(i, u, D = 0, s = 0) {
|
|
270
|
+
const l = new ArrayBuffer(14), A = new DataView(l);
|
|
271
271
|
let o = 0;
|
|
272
|
-
return
|
|
272
|
+
return A.setUint8(o, Q.INJECT_KEYCODE), o += 1, A.setUint8(o, i), o += 1, A.setInt32(o, u, !0), o += 4, A.setInt32(o, D, !0), o += 4, A.setInt32(o, s, !0), l;
|
|
273
273
|
}
|
|
274
|
-
const
|
|
275
|
-
window.debugRemoteControl && console.log(...
|
|
276
|
-
}, Y = (...
|
|
277
|
-
window.debugRemoteControl && console.warn(...
|
|
274
|
+
const T = (...i) => {
|
|
275
|
+
window.debugRemoteControl && console.log(...i);
|
|
276
|
+
}, Y = (...i) => {
|
|
277
|
+
window.debugRemoteControl && console.warn(...i);
|
|
278
278
|
};
|
|
279
|
-
function
|
|
280
|
-
const
|
|
279
|
+
function pe(i) {
|
|
280
|
+
const u = i.code, D = oe[u];
|
|
281
281
|
if (!D)
|
|
282
|
-
return Y(`Unknown event.code: ${
|
|
283
|
-
let
|
|
284
|
-
const l =
|
|
282
|
+
return Y(`Unknown event.code: ${u}, key: ${i.key}`), null;
|
|
283
|
+
let s = e.META_NONE;
|
|
284
|
+
const l = u >= "KeyA" && u <= "KeyZ", A = i.getModifierState("CapsLock"), o = i.shiftKey;
|
|
285
285
|
let c = o;
|
|
286
|
-
return l && (c = o !==
|
|
286
|
+
return l && (c = o !== A), c && (s |= e.META_SHIFT_ON), i.ctrlKey && (s |= e.META_CTRL_ON), i.altKey && (s |= e.META_ALT_ON), i.metaKey && (s |= e.META_META_ON), { keycode: D, metaState: s };
|
|
287
287
|
}
|
|
288
|
-
const Te =
|
|
289
|
-
({ className:
|
|
290
|
-
const o =
|
|
291
|
-
() =>
|
|
292
|
-
[
|
|
293
|
-
),
|
|
294
|
-
|
|
295
|
-
},
|
|
296
|
-
!
|
|
297
|
-
},
|
|
298
|
-
if (
|
|
288
|
+
const Te = Ce(
|
|
289
|
+
({ className: i, url: u, token: D, sessionId: s, openUrl: l }, A) => {
|
|
290
|
+
const o = P(null), c = P(null), d = P(null), C = P(null), [N, _] = De(!1), k = P(void 0), I = P(/* @__PURE__ */ new Map()), U = P(/* @__PURE__ */ new Map()), m = P(/* @__PURE__ */ new Map()), h = Oe(
|
|
291
|
+
() => s || Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15),
|
|
292
|
+
[s]
|
|
293
|
+
), O = (n) => {
|
|
294
|
+
T(n);
|
|
295
|
+
}, L = (n) => {
|
|
296
|
+
!C.current || C.current.readyState !== "open" || C.current.send(n);
|
|
297
|
+
}, S = (n) => {
|
|
298
|
+
if (n.preventDefault(), n.stopPropagation(), !C.current || C.current.readyState !== "open" || !o.current)
|
|
299
299
|
return;
|
|
300
|
-
const
|
|
301
|
-
if (!
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
let v =
|
|
305
|
-
W > ae ? H =
|
|
306
|
-
const de = (
|
|
300
|
+
const K = o.current, E = K.getBoundingClientRect(), t = K.videoWidth, r = K.videoHeight;
|
|
301
|
+
if (!t || !r) return;
|
|
302
|
+
const p = (a, f, y, g) => {
|
|
303
|
+
const M = E.width, F = E.height, W = t / r, ae = M / F;
|
|
304
|
+
let v = M, H = F;
|
|
305
|
+
W > ae ? H = M / W : v = F * W;
|
|
306
|
+
const de = (M - v) / 2, ue = (F - H) / 2, $ = f - E.left - de, V = y - E.top - ue, G = $ >= 0 && $ <= v && V >= 0 && V <= H;
|
|
307
307
|
let J = 0, x = 0;
|
|
308
|
-
G && (J = Math.max(0, Math.min(
|
|
309
|
-
let
|
|
310
|
-
const ee =
|
|
311
|
-
switch (
|
|
308
|
+
G && (J = Math.max(0, Math.min(t, $ / v * t)), x = Math.max(0, Math.min(r, V / H * r)));
|
|
309
|
+
let w = null, R = null, _e = 1;
|
|
310
|
+
const ee = b.BUTTON_PRIMARY;
|
|
311
|
+
switch (g) {
|
|
312
312
|
case "down":
|
|
313
|
-
G ? (
|
|
313
|
+
G ? (w = b.ACTION_DOWN, R = { x: J, y: x }, m.current.set(a, R), a === -1 && o.current?.focus()) : m.current.delete(a);
|
|
314
314
|
break;
|
|
315
315
|
case "move":
|
|
316
|
-
|
|
316
|
+
m.current.has(a) && G && (w = b.ACTION_MOVE, R = { x: J, y: x }, m.current.set(a, R));
|
|
317
317
|
break;
|
|
318
318
|
case "up":
|
|
319
319
|
case "cancel":
|
|
320
|
-
|
|
320
|
+
m.current.has(a) && (w = g === "cancel" ? b.ACTION_CANCEL : b.ACTION_UP, R = m.current.get(a), m.current.delete(a));
|
|
321
321
|
break;
|
|
322
322
|
}
|
|
323
|
-
if (
|
|
324
|
-
const te =
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
323
|
+
if (w !== null && R !== null) {
|
|
324
|
+
const te = Ye(
|
|
325
|
+
w,
|
|
326
|
+
a,
|
|
327
|
+
t,
|
|
328
|
+
r,
|
|
329
|
+
R.x,
|
|
330
|
+
R.y,
|
|
331
331
|
_e,
|
|
332
332
|
ee,
|
|
333
333
|
ee
|
|
334
334
|
);
|
|
335
|
-
te &&
|
|
336
|
-
} else (
|
|
335
|
+
te && L(te);
|
|
336
|
+
} else (g === "up" || g === "cancel") && m.current.delete(a);
|
|
337
337
|
};
|
|
338
|
-
if ("touches" in
|
|
339
|
-
const
|
|
340
|
-
let
|
|
341
|
-
switch (
|
|
338
|
+
if ("touches" in n) {
|
|
339
|
+
const a = n.changedTouches;
|
|
340
|
+
let f;
|
|
341
|
+
switch (n.type) {
|
|
342
342
|
case "touchstart":
|
|
343
|
-
|
|
343
|
+
f = "down";
|
|
344
344
|
break;
|
|
345
345
|
case "touchmove":
|
|
346
|
-
|
|
346
|
+
f = "move";
|
|
347
347
|
break;
|
|
348
348
|
case "touchend":
|
|
349
|
-
|
|
349
|
+
f = "up";
|
|
350
350
|
break;
|
|
351
351
|
case "touchcancel":
|
|
352
|
-
|
|
352
|
+
f = "cancel";
|
|
353
353
|
break;
|
|
354
354
|
default:
|
|
355
355
|
return;
|
|
356
356
|
}
|
|
357
|
-
for (let
|
|
358
|
-
const
|
|
359
|
-
|
|
357
|
+
for (let y = 0; y < a.length; y++) {
|
|
358
|
+
const g = a[y];
|
|
359
|
+
p(g.identifier, g.clientX, g.clientY, f);
|
|
360
360
|
}
|
|
361
361
|
} else {
|
|
362
|
-
let
|
|
363
|
-
switch (
|
|
362
|
+
let f = null;
|
|
363
|
+
switch (n.type) {
|
|
364
364
|
case "mousedown":
|
|
365
|
-
|
|
365
|
+
n.button === 0 && (f = "down");
|
|
366
366
|
break;
|
|
367
367
|
case "mousemove":
|
|
368
|
-
|
|
368
|
+
m.current.has(-1) && (f = "move");
|
|
369
369
|
break;
|
|
370
370
|
case "mouseup":
|
|
371
|
-
|
|
371
|
+
n.button === 0 && (f = "up");
|
|
372
372
|
break;
|
|
373
373
|
case "mouseleave":
|
|
374
|
-
|
|
374
|
+
m.current.has(-1) && (f = "up");
|
|
375
375
|
break;
|
|
376
376
|
}
|
|
377
|
-
|
|
377
|
+
f && p(-1, n.clientX, n.clientY, f);
|
|
378
378
|
}
|
|
379
|
-
}, X = (
|
|
380
|
-
if (
|
|
381
|
-
type:
|
|
382
|
-
key:
|
|
383
|
-
keyCode:
|
|
384
|
-
code:
|
|
385
|
-
target:
|
|
379
|
+
}, X = (n) => {
|
|
380
|
+
if (n.preventDefault(), n.stopPropagation(), T("Keyboard event:", {
|
|
381
|
+
type: n.type,
|
|
382
|
+
key: n.key,
|
|
383
|
+
keyCode: n.keyCode,
|
|
384
|
+
code: n.code,
|
|
385
|
+
target: n.target.tagName,
|
|
386
386
|
focused: document.activeElement === o.current
|
|
387
387
|
}), document.activeElement !== o.current) {
|
|
388
388
|
Y("Video element not focused, skipping keyboard event");
|
|
389
389
|
return;
|
|
390
390
|
}
|
|
391
|
-
if (!
|
|
392
|
-
Y("Data channel not ready for keyboard event:",
|
|
391
|
+
if (!C.current || C.current.readyState !== "open") {
|
|
392
|
+
Y("Data channel not ready for keyboard event:", C.current?.readyState);
|
|
393
393
|
return;
|
|
394
394
|
}
|
|
395
|
-
if (
|
|
396
|
-
if (
|
|
397
|
-
|
|
398
|
-
if (
|
|
399
|
-
|
|
395
|
+
if (n.type === "keydown") {
|
|
396
|
+
if (n.key.toLowerCase() === "v" && (n.metaKey || n.ctrlKey)) {
|
|
397
|
+
T("Paste shortcut detected"), navigator.clipboard.readText().then((E) => {
|
|
398
|
+
if (E) {
|
|
399
|
+
T(
|
|
400
400
|
"Pasting text via SET_CLIPBOARD:",
|
|
401
|
-
|
|
401
|
+
E.substring(0, 20) + (E.length > 20 ? "..." : "")
|
|
402
402
|
);
|
|
403
|
-
const
|
|
404
|
-
|
|
403
|
+
const t = Ae(E, !0);
|
|
404
|
+
L(t);
|
|
405
405
|
}
|
|
406
|
-
}).catch((
|
|
407
|
-
console.error("Failed to read clipboard contents: ",
|
|
406
|
+
}).catch((E) => {
|
|
407
|
+
console.error("Failed to read clipboard contents: ", E);
|
|
408
408
|
});
|
|
409
409
|
return;
|
|
410
410
|
}
|
|
411
|
-
if (
|
|
412
|
-
|
|
413
|
-
const
|
|
411
|
+
if (n.key.toLowerCase() === "m" && (n.metaKey || n.ctrlKey)) {
|
|
412
|
+
T("Menu shortcut detected");
|
|
413
|
+
const E = B(
|
|
414
414
|
e.ACTION_DOWN,
|
|
415
415
|
e.MENU,
|
|
416
416
|
0,
|
|
417
417
|
e.META_NONE
|
|
418
418
|
// Modifiers are handled by the shortcut check, not passed down
|
|
419
419
|
);
|
|
420
|
-
|
|
421
|
-
const
|
|
420
|
+
L(E);
|
|
421
|
+
const t = B(
|
|
422
422
|
e.ACTION_UP,
|
|
423
423
|
e.MENU,
|
|
424
424
|
0,
|
|
425
425
|
e.META_NONE
|
|
426
426
|
);
|
|
427
|
-
|
|
427
|
+
L(t);
|
|
428
428
|
return;
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
|
-
const
|
|
432
|
-
if (
|
|
433
|
-
const { keycode:
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
431
|
+
const K = pe(n);
|
|
432
|
+
if (K) {
|
|
433
|
+
const { keycode: E, metaState: t } = K, r = n.type === "keydown" ? e.ACTION_DOWN : e.ACTION_UP;
|
|
434
|
+
T(`Sending Keycode: key=${n.key}, code=${E}, action=${r}, meta=${t}`);
|
|
435
|
+
const p = B(
|
|
436
|
+
r,
|
|
437
|
+
E,
|
|
438
438
|
0,
|
|
439
439
|
// repeat count, typically 0 for single presses
|
|
440
|
-
|
|
440
|
+
t
|
|
441
441
|
);
|
|
442
|
-
|
|
442
|
+
L(p);
|
|
443
443
|
} else
|
|
444
|
-
|
|
444
|
+
T(`Ignoring unhandled key event: type=${n.type}, key=${n.key}`);
|
|
445
445
|
}, ce = () => {
|
|
446
446
|
c.current && c.current.readyState === WebSocket.OPEN && c.current.send(
|
|
447
447
|
JSON.stringify({
|
|
448
448
|
type: "keepAlive",
|
|
449
|
-
sessionId:
|
|
449
|
+
sessionId: h
|
|
450
450
|
})
|
|
451
451
|
);
|
|
452
452
|
}, j = () => {
|
|
453
|
-
|
|
453
|
+
k.current && window.clearInterval(k.current), k.current = window.setInterval(ce, 1e4);
|
|
454
454
|
}, Z = () => {
|
|
455
|
-
|
|
455
|
+
k.current && (window.clearInterval(k.current), k.current = void 0);
|
|
456
456
|
}, z = () => {
|
|
457
457
|
document.hidden ? Z() : j();
|
|
458
458
|
}, Ee = async () => {
|
|
459
459
|
try {
|
|
460
|
-
c.current = new WebSocket(`${
|
|
461
|
-
|
|
460
|
+
c.current = new WebSocket(`${u}?token=${D}`), c.current.onerror = (t) => {
|
|
461
|
+
O("WebSocket error: " + t);
|
|
462
462
|
}, c.current.onclose = () => {
|
|
463
|
-
|
|
464
|
-
}, await new Promise((t,
|
|
465
|
-
c.current && (c.current.onopen = t, setTimeout(() =>
|
|
463
|
+
O("WebSocket closed");
|
|
464
|
+
}, await new Promise((t, r) => {
|
|
465
|
+
c.current && (c.current.onopen = t, setTimeout(() => r(new Error("WebSocket connection timeout")), 5e3));
|
|
466
466
|
});
|
|
467
|
-
const
|
|
468
|
-
const
|
|
467
|
+
const K = await new Promise((t, r) => {
|
|
468
|
+
const p = setTimeout(() => r(new Error("RTCConfiguration timeout")), 5e3), a = (f) => {
|
|
469
469
|
try {
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
} catch (
|
|
473
|
-
console.error("Error handling RTC configuration:",
|
|
470
|
+
const y = JSON.parse(f.data);
|
|
471
|
+
y.type === "rtcConfiguration" && (clearTimeout(p), c.current?.removeEventListener("message", a), t(y.rtcConfiguration));
|
|
472
|
+
} catch (y) {
|
|
473
|
+
console.error("Error handling RTC configuration:", y), r(y);
|
|
474
474
|
}
|
|
475
475
|
};
|
|
476
|
-
c.current?.addEventListener("message",
|
|
476
|
+
c.current?.addEventListener("message", a), c.current?.send(
|
|
477
477
|
JSON.stringify({
|
|
478
478
|
type: "requestRtcConfiguration",
|
|
479
|
-
sessionId:
|
|
479
|
+
sessionId: h
|
|
480
480
|
})
|
|
481
481
|
);
|
|
482
482
|
});
|
|
483
|
-
|
|
483
|
+
d.current = new RTCPeerConnection(K), d.current.addTransceiver("audio", { direction: "recvonly" });
|
|
484
|
+
const E = d.current.addTransceiver("video", { direction: "recvonly" });
|
|
485
|
+
if (RTCRtpReceiver.getCapabilities) {
|
|
486
|
+
const t = RTCRtpReceiver.getCapabilities("video");
|
|
487
|
+
if (t && t.codecs) {
|
|
488
|
+
const p = t.codecs.sort((a, f) => {
|
|
489
|
+
const y = (g) => {
|
|
490
|
+
const M = g.mimeType.toLowerCase();
|
|
491
|
+
return M.includes("vp9") ? 1 : M.includes("h265") || M.includes("hevc") ? 2 : M.includes("h264") || M.includes("avc") ? 3 : 4;
|
|
492
|
+
};
|
|
493
|
+
return y(a) - y(f);
|
|
494
|
+
});
|
|
495
|
+
E.setCodecPreferences(p), T("Set codec preferences:", p.map((a) => a.mimeType).join(", "));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (C.current = d.current.createDataChannel("control", {
|
|
484
499
|
ordered: !0,
|
|
485
500
|
negotiated: !0,
|
|
486
501
|
id: 1
|
|
487
|
-
}),
|
|
488
|
-
if (
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
})
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
},
|
|
514
|
-
|
|
515
|
-
},
|
|
516
|
-
|
|
517
|
-
},
|
|
502
|
+
}), C.current.onopen = () => {
|
|
503
|
+
if (O("Control channel opened"), c.current) {
|
|
504
|
+
for (let t = 0; t < 12; t++)
|
|
505
|
+
setTimeout(() => {
|
|
506
|
+
c.current && c.current.send(JSON.stringify({ type: "requestFrame", sessionId: h }));
|
|
507
|
+
}, t * 125);
|
|
508
|
+
if (l)
|
|
509
|
+
try {
|
|
510
|
+
const t = decodeURIComponent(l);
|
|
511
|
+
O("Opening URL"), c.current.send(
|
|
512
|
+
JSON.stringify({
|
|
513
|
+
type: "openUrl",
|
|
514
|
+
url: t,
|
|
515
|
+
sessionId: h
|
|
516
|
+
})
|
|
517
|
+
);
|
|
518
|
+
} catch (t) {
|
|
519
|
+
console.error({ error: t }, "Error decoding URL, falling back to the original URL"), c.current.send(
|
|
520
|
+
JSON.stringify({
|
|
521
|
+
type: "openUrl",
|
|
522
|
+
url: l,
|
|
523
|
+
sessionId: h
|
|
524
|
+
})
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}, C.current.onclose = () => {
|
|
529
|
+
O("Control channel closed");
|
|
530
|
+
}, C.current.onerror = (t) => {
|
|
531
|
+
console.error("Control channel error:", t), O("Control channel error: " + t);
|
|
532
|
+
}, d.current.onconnectionstatechange = () => {
|
|
533
|
+
O("Connection state: " + d.current?.connectionState), _(d.current?.connectionState === "connected");
|
|
534
|
+
}, d.current.oniceconnectionstatechange = () => {
|
|
535
|
+
O("ICE state: " + d.current?.iceConnectionState);
|
|
536
|
+
}, d.current.ontrack = (t) => {
|
|
537
|
+
O("Received remote track: " + t.track.kind), t.track.kind === "video" && o.current && (T(`[${(/* @__PURE__ */ new Date()).toISOString()}] Video track received:`, t.track), o.current.srcObject = t.streams[0]);
|
|
538
|
+
}, d.current.onicecandidate = (t) => {
|
|
518
539
|
if (t.candidate && c.current) {
|
|
519
|
-
const
|
|
540
|
+
const r = {
|
|
520
541
|
type: "candidate",
|
|
521
542
|
candidate: t.candidate.candidate,
|
|
522
543
|
sdpMid: t.candidate.sdpMid,
|
|
523
544
|
sdpMLineIndex: t.candidate.sdpMLineIndex,
|
|
524
|
-
sessionId:
|
|
545
|
+
sessionId: h
|
|
525
546
|
};
|
|
526
|
-
c.current.send(JSON.stringify(
|
|
547
|
+
c.current.send(JSON.stringify(r)), O("Sent ICE candidate");
|
|
527
548
|
} else
|
|
528
|
-
|
|
549
|
+
O("ICE candidate gathering completed");
|
|
529
550
|
}, c.current.onmessage = async (t) => {
|
|
530
|
-
let
|
|
551
|
+
let r;
|
|
531
552
|
try {
|
|
532
|
-
|
|
533
|
-
} catch (
|
|
534
|
-
Y("Error parsing message:",
|
|
553
|
+
r = JSON.parse(t.data);
|
|
554
|
+
} catch (p) {
|
|
555
|
+
Y("Error parsing message:", p);
|
|
535
556
|
return;
|
|
536
557
|
}
|
|
537
|
-
switch (
|
|
558
|
+
switch (O("Received: " + r.type), r.type) {
|
|
538
559
|
case "answer":
|
|
539
|
-
if (!
|
|
540
|
-
|
|
560
|
+
if (!d.current) {
|
|
561
|
+
O("No peer connection, skipping answer");
|
|
541
562
|
break;
|
|
542
563
|
}
|
|
543
|
-
await
|
|
564
|
+
await d.current.setRemoteDescription(
|
|
544
565
|
new RTCSessionDescription({
|
|
545
566
|
type: "answer",
|
|
546
|
-
sdp:
|
|
567
|
+
sdp: r.sdp
|
|
547
568
|
})
|
|
548
|
-
),
|
|
569
|
+
), O("Set remote description");
|
|
549
570
|
break;
|
|
550
571
|
case "candidate":
|
|
551
|
-
if (!
|
|
552
|
-
|
|
572
|
+
if (!d.current) {
|
|
573
|
+
O("No peer connection, skipping candidate");
|
|
553
574
|
break;
|
|
554
575
|
}
|
|
555
|
-
await
|
|
576
|
+
await d.current.addIceCandidate(
|
|
556
577
|
new RTCIceCandidate({
|
|
557
|
-
candidate:
|
|
558
|
-
sdpMid:
|
|
559
|
-
sdpMLineIndex:
|
|
578
|
+
candidate: r.candidate,
|
|
579
|
+
sdpMid: r.sdpMid,
|
|
580
|
+
sdpMLineIndex: r.sdpMLineIndex
|
|
560
581
|
})
|
|
561
|
-
),
|
|
582
|
+
), O("Added ICE candidate");
|
|
562
583
|
break;
|
|
563
584
|
case "screenshot":
|
|
564
|
-
if (typeof
|
|
565
|
-
Y("Received invalid screenshot success message:",
|
|
585
|
+
if (typeof r.id != "string" || typeof r.dataUri != "string") {
|
|
586
|
+
Y("Received invalid screenshot success message:", r);
|
|
566
587
|
break;
|
|
567
588
|
}
|
|
568
|
-
const
|
|
569
|
-
if (!
|
|
570
|
-
Y(`Received screenshot data for unknown or handled id: ${
|
|
589
|
+
const p = I.current.get(r.id);
|
|
590
|
+
if (!p) {
|
|
591
|
+
Y(`Received screenshot data for unknown or handled id: ${r.id}`);
|
|
571
592
|
break;
|
|
572
593
|
}
|
|
573
|
-
|
|
594
|
+
T(`Received screenshot data for id ${r.id}`), p({ dataUri: r.dataUri }), I.current.delete(r.id), U.current.delete(r.id);
|
|
574
595
|
break;
|
|
575
596
|
case "screenshotError":
|
|
576
|
-
if (typeof
|
|
577
|
-
Y("Received invalid screenshot error message:",
|
|
597
|
+
if (typeof r.id != "string" || typeof r.message != "string") {
|
|
598
|
+
Y("Received invalid screenshot error message:", r);
|
|
578
599
|
break;
|
|
579
600
|
}
|
|
580
|
-
const
|
|
581
|
-
if (!
|
|
582
|
-
Y(`Received screenshot error for unknown or handled id: ${
|
|
601
|
+
const a = U.current.get(r.id);
|
|
602
|
+
if (!a) {
|
|
603
|
+
Y(`Received screenshot error for unknown or handled id: ${r.id}`);
|
|
583
604
|
break;
|
|
584
605
|
}
|
|
585
|
-
Y(`Received screenshot error for id ${
|
|
606
|
+
Y(`Received screenshot error for id ${r.id}: ${r.message}`), a(new Error(r.message)), I.current.delete(r.id), U.current.delete(r.id);
|
|
586
607
|
break;
|
|
587
608
|
default:
|
|
588
|
-
Y(`Received unhandled message type: ${
|
|
609
|
+
Y(`Received unhandled message type: ${r.type}`, r);
|
|
589
610
|
break;
|
|
590
611
|
}
|
|
591
|
-
},
|
|
592
|
-
const t = await
|
|
612
|
+
}, d.current) {
|
|
613
|
+
const t = await d.current.createOffer({
|
|
593
614
|
offerToReceiveVideo: !0,
|
|
594
615
|
offerToReceiveAudio: !1
|
|
595
616
|
});
|
|
596
|
-
await
|
|
617
|
+
await d.current.setLocalDescription(t), c.current && c.current.send(
|
|
597
618
|
JSON.stringify({
|
|
598
619
|
type: "offer",
|
|
599
620
|
sdp: t.sdp,
|
|
600
|
-
sessionId:
|
|
621
|
+
sessionId: h
|
|
601
622
|
})
|
|
602
|
-
),
|
|
623
|
+
), O("Sent offer");
|
|
603
624
|
}
|
|
604
|
-
} catch (
|
|
605
|
-
|
|
625
|
+
} catch (n) {
|
|
626
|
+
O("Error: " + n);
|
|
606
627
|
}
|
|
607
628
|
}, ie = () => {
|
|
608
|
-
c.current && (c.current.close(), c.current = null),
|
|
629
|
+
c.current && (c.current.close(), c.current = null), d.current && (d.current.close(), d.current = null), o.current && (o.current.srcObject = null), C.current && (C.current.close(), C.current = null), _(!1), O("Stopped");
|
|
609
630
|
};
|
|
610
631
|
Ke(() => (Ee(), document.hidden || j(), document.addEventListener("visibilitychange", z), () => {
|
|
611
632
|
Z(), ie(), document.removeEventListener("visibilitychange", z);
|
|
612
|
-
}), [
|
|
633
|
+
}), [u, D, s]);
|
|
613
634
|
const se = () => {
|
|
614
635
|
o.current && o.current.focus();
|
|
615
636
|
};
|
|
616
|
-
return le(
|
|
617
|
-
openUrl: (
|
|
637
|
+
return le(A, () => ({
|
|
638
|
+
openUrl: (n) => {
|
|
618
639
|
if (!c.current || c.current.readyState !== WebSocket.OPEN) {
|
|
619
640
|
Y("WebSocket not open, cannot send open_url command via ref.");
|
|
620
641
|
return;
|
|
621
642
|
}
|
|
622
643
|
try {
|
|
623
|
-
const
|
|
624
|
-
|
|
644
|
+
const K = decodeURIComponent(n);
|
|
645
|
+
O("Opening URL"), c.current.send(
|
|
625
646
|
JSON.stringify({
|
|
626
647
|
type: "openUrl",
|
|
627
|
-
url:
|
|
628
|
-
sessionId:
|
|
648
|
+
url: K,
|
|
649
|
+
sessionId: h
|
|
629
650
|
})
|
|
630
651
|
);
|
|
631
|
-
} catch (
|
|
632
|
-
Y("Error decoding or sending URL via ref:", { error:
|
|
652
|
+
} catch (K) {
|
|
653
|
+
Y("Error decoding or sending URL via ref:", { error: K, url: n }), c.current.send(
|
|
633
654
|
JSON.stringify({
|
|
634
655
|
type: "openUrl",
|
|
635
|
-
url:
|
|
636
|
-
sessionId:
|
|
656
|
+
url: n,
|
|
657
|
+
sessionId: h
|
|
637
658
|
})
|
|
638
659
|
);
|
|
639
660
|
}
|
|
640
661
|
},
|
|
641
|
-
sendKeyEvent: (
|
|
642
|
-
if (!
|
|
643
|
-
Y("Data channel not ready for imperative key command:",
|
|
662
|
+
sendKeyEvent: (n) => {
|
|
663
|
+
if (!C.current || C.current.readyState !== "open") {
|
|
664
|
+
Y("Data channel not ready for imperative key command:", C.current?.readyState);
|
|
644
665
|
return;
|
|
645
666
|
}
|
|
646
|
-
const
|
|
647
|
-
if (!
|
|
648
|
-
Y(`Unknown event.code for imperative command: ${
|
|
667
|
+
const K = oe[n.code];
|
|
668
|
+
if (!K) {
|
|
669
|
+
Y(`Unknown event.code for imperative command: ${n.code}`);
|
|
649
670
|
return;
|
|
650
671
|
}
|
|
651
|
-
let
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
`Sending Imperative Key Command: code=${
|
|
672
|
+
let E = e.META_NONE;
|
|
673
|
+
n.shiftKey && (E |= e.META_SHIFT_ON), n.altKey && (E |= e.META_ALT_ON), n.ctrlKey && (E |= e.META_CTRL_ON), n.metaKey && (E |= e.META_META_ON);
|
|
674
|
+
const t = n.type === "keydown" ? e.ACTION_DOWN : e.ACTION_UP;
|
|
675
|
+
T(
|
|
676
|
+
`Sending Imperative Key Command: code=${n.code}, keycode=${K}, action=${t}, meta=${E}`
|
|
656
677
|
);
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
678
|
+
const r = B(
|
|
679
|
+
t,
|
|
680
|
+
K,
|
|
660
681
|
0,
|
|
661
682
|
// repeat count, typically 0 for single presses
|
|
662
|
-
|
|
683
|
+
E
|
|
663
684
|
);
|
|
664
|
-
|
|
685
|
+
r && L(r);
|
|
665
686
|
},
|
|
666
|
-
screenshot: () => new Promise((
|
|
687
|
+
screenshot: () => new Promise((n, K) => {
|
|
667
688
|
if (!c.current || c.current.readyState !== WebSocket.OPEN)
|
|
668
|
-
return Y("WebSocket not open, cannot send screenshot command."),
|
|
669
|
-
const
|
|
689
|
+
return Y("WebSocket not open, cannot send screenshot command."), K(new Error("WebSocket is not connected or connection is not open."));
|
|
690
|
+
const E = `ui-ss-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, t = {
|
|
670
691
|
type: "screenshot",
|
|
671
692
|
// Matches the type expected by instance API
|
|
672
|
-
id:
|
|
693
|
+
id: E
|
|
673
694
|
};
|
|
674
|
-
|
|
695
|
+
I.current.set(E, n), U.current.set(E, K), T("Sending screenshot request:", t);
|
|
675
696
|
try {
|
|
676
|
-
c.current.send(JSON.stringify(
|
|
677
|
-
} catch (
|
|
678
|
-
Y("Failed to send screenshot request immediately:",
|
|
697
|
+
c.current.send(JSON.stringify(t));
|
|
698
|
+
} catch (r) {
|
|
699
|
+
Y("Failed to send screenshot request immediately:", r), I.current.delete(E), U.current.delete(E), K(r);
|
|
679
700
|
return;
|
|
680
701
|
}
|
|
681
702
|
setTimeout(() => {
|
|
682
|
-
|
|
703
|
+
I.current.has(E) && (Y(`Screenshot request timed out for id ${E}`), U.current.get(E)?.(new Error("Screenshot request timed out")), I.current.delete(E), U.current.delete(E));
|
|
683
704
|
}, 3e4);
|
|
684
705
|
})
|
|
685
706
|
})), /* @__PURE__ */ ne(
|
|
686
707
|
"div",
|
|
687
708
|
{
|
|
688
|
-
className:
|
|
709
|
+
className: fe(
|
|
689
710
|
"rc-container",
|
|
690
711
|
// Use custom CSS class instead of Tailwind
|
|
691
|
-
|
|
712
|
+
i
|
|
692
713
|
),
|
|
693
714
|
style: { touchAction: "none" },
|
|
694
|
-
onMouseDown:
|
|
695
|
-
onMouseMove:
|
|
696
|
-
onMouseUp:
|
|
697
|
-
onMouseLeave:
|
|
698
|
-
onTouchStart:
|
|
699
|
-
onTouchMove:
|
|
700
|
-
onTouchEnd:
|
|
701
|
-
onTouchCancel:
|
|
715
|
+
onMouseDown: S,
|
|
716
|
+
onMouseMove: S,
|
|
717
|
+
onMouseUp: S,
|
|
718
|
+
onMouseLeave: S,
|
|
719
|
+
onTouchStart: S,
|
|
720
|
+
onTouchMove: S,
|
|
721
|
+
onTouchEnd: S,
|
|
722
|
+
onTouchCancel: S,
|
|
702
723
|
children: [
|
|
703
724
|
/* @__PURE__ */ q(
|
|
704
725
|
"video",
|
|
@@ -721,7 +742,7 @@ const Te = De(
|
|
|
721
742
|
}
|
|
722
743
|
}
|
|
723
744
|
),
|
|
724
|
-
!
|
|
745
|
+
!N && /* @__PURE__ */ ne("div", { className: "rc-placeholder-wrapper", children: [
|
|
725
746
|
/* @__PURE__ */ q("div", { className: "rc-spinner" }),
|
|
726
747
|
/* @__PURE__ */ q("p", { className: "rc-placeholder-content", children: "Connecting..." })
|
|
727
748
|
] })
|
package/package.json
CHANGED
|
@@ -486,7 +486,33 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
486
486
|
const rtcConfig = await rtcConfigPromise;
|
|
487
487
|
peerConnectionRef.current = new RTCPeerConnection(rtcConfig);
|
|
488
488
|
peerConnectionRef.current.addTransceiver('audio', { direction: 'recvonly' });
|
|
489
|
-
peerConnectionRef.current.addTransceiver('video', { direction: 'recvonly' });
|
|
489
|
+
const videoTransceiver = peerConnectionRef.current.addTransceiver('video', { direction: 'recvonly' });
|
|
490
|
+
|
|
491
|
+
// As hardware encoder, we use H265 for iOS and VP9 for Android.
|
|
492
|
+
// We make sure these two are the first ones in the list.
|
|
493
|
+
// If not, the fallback is H264 which is also hardware accelerated, although not as good,
|
|
494
|
+
// available on all platforms.
|
|
495
|
+
//
|
|
496
|
+
// The rest is not important.
|
|
497
|
+
if (RTCRtpReceiver.getCapabilities) {
|
|
498
|
+
const capabilities = RTCRtpReceiver.getCapabilities('video');
|
|
499
|
+
if (capabilities && capabilities.codecs) {
|
|
500
|
+
const codecs = capabilities.codecs;
|
|
501
|
+
const sortedCodecs = codecs.sort((a, b) => {
|
|
502
|
+
const getCodecPriority = (codec: { mimeType: string }): number => {
|
|
503
|
+
const mimeType = codec.mimeType.toLowerCase();
|
|
504
|
+
if (mimeType.includes('vp9')) return 1;
|
|
505
|
+
if (mimeType.includes('h265') || mimeType.includes('hevc')) return 2;
|
|
506
|
+
if (mimeType.includes('h264') || mimeType.includes('avc')) return 3;
|
|
507
|
+
return 4; // Everything else
|
|
508
|
+
};
|
|
509
|
+
return getCodecPriority(a) - getCodecPriority(b);
|
|
510
|
+
});
|
|
511
|
+
videoTransceiver.setCodecPreferences(sortedCodecs);
|
|
512
|
+
debugLog('Set codec preferences:', sortedCodecs.map(c => c.mimeType).join(', '));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
490
516
|
dataChannelRef.current = peerConnectionRef.current.createDataChannel('control', {
|
|
491
517
|
ordered: true,
|
|
492
518
|
negotiated: true,
|
|
@@ -497,7 +523,13 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
|
|
|
497
523
|
updateStatus('Control channel opened');
|
|
498
524
|
// Request first frame once we're ready to receive video
|
|
499
525
|
if (wsRef.current) {
|
|
500
|
-
|
|
526
|
+
for (let i = 0; i < 12; i++) {
|
|
527
|
+
setTimeout(() => {
|
|
528
|
+
if (wsRef.current) {
|
|
529
|
+
wsRef.current.send(JSON.stringify({ type: 'requestFrame', sessionId: sessionId }));
|
|
530
|
+
}
|
|
531
|
+
}, i * 125); // 125ms = quarter second
|
|
532
|
+
}
|
|
501
533
|
|
|
502
534
|
// Send openUrl message if the prop is provided
|
|
503
535
|
if (openUrl) {
|