전송 메시지 rtc 로 변경

This commit is contained in:
2025-10-22 14:57:49 +09:00
parent c5e32808f4
commit 08f6c8900d
2 changed files with 81 additions and 40 deletions

View File

@@ -8,10 +8,11 @@ export default function RemoteControl() {
const [status, setStatus] = useState('disconnected'); // 'disconnected' | 'connecting' | 'connected' const [status, setStatus] = useState('disconnected'); // 'disconnected' | 'connecting' | 'connected'
const [displays, setDisplays] = useState([]); const [displays, setDisplays] = useState([]);
const [selectedDisplay, setSelectedDisplay] = useState(null); const [selectedDisplay, setSelectedDisplay] = useState(null);
const [isControlling, setIsControlling] = useState(true); const [isControlling, setIsControlling] = useState(false);
const videoRef = useRef(null); const videoRef = useRef(null);
const socketRef = useRef(null); const socketRef = useRef(null);
const dataChannelRef = useRef(null);
const peerConnectionRef = useRef(null); const peerConnectionRef = useRef(null);
const EMPLOYEE_ID = "psn14020_client"; const EMPLOYEE_ID = "psn14020_client";
@@ -46,6 +47,7 @@ export default function RemoteControl() {
setStatus('connected'); setStatus('connected');
setIsControlling(true); setIsControlling(true);
videoRef.current.requestFullscreen();
}) })
socket.on('icecandidate', async(data) => { socket.on('icecandidate', async(data) => {
@@ -58,6 +60,17 @@ export default function RemoteControl() {
setStatus('disconnected'); setStatus('disconnected');
}); });
socket.on('disconnect', () => {
setStatus('disconnected');
setIsControlling(false);
if(peerConnectionRef.current) {
socket.emit('forceClose', {targetId: employeeId, server: 'client'})
}
peerConnectionRef.current.close();
peerConnectionRef.current = null;
videoRef.current.requestFullscreen();
})
return () => { return () => {
socket.disconnect(); socket.disconnect();
}; };
@@ -70,6 +83,7 @@ export default function RemoteControl() {
iceServers: [{urls: 'stun:stun.l.google.com:19302'}] iceServers: [{urls: 'stun:stun.l.google.com:19302'}]
}); });
dataChannelRef.current = await peerConnectionRef.current.createDataChannel("dataChannel", null);
const offer = await peerConnectionRef.current.createOffer({offerToReceiveVideo: true}) const offer = await peerConnectionRef.current.createOffer({offerToReceiveVideo: true})
await peerConnectionRef.current.setLocalDescription(offer); await peerConnectionRef.current.setLocalDescription(offer);
@@ -92,11 +106,16 @@ export default function RemoteControl() {
socketRef.current.emit('icecandidate', {type: 'icecandidate', targetId: employeeId, server:'client', 'candidate': event.candidate}); socketRef.current.emit('icecandidate', {type: 'icecandidate', targetId: employeeId, server:'client', 'candidate': event.candidate});
}, 2000) }, 2000)
} }
}; };
let nextInput = true;
// 🖱️ 입력 이벤트 전달 // 🖱️ 입력 이벤트 전달
const handleMouseEvent = (e) => { const handleMouseEvent = (e) => {
if (!isControlling || !videoRef.current) return; if (!isControlling || !videoRef.current) return;
e.preventDefault();
const video = videoRef.current; const video = videoRef.current;
const rect = video.getBoundingClientRect(); const rect = video.getBoundingClientRect();
@@ -108,19 +127,31 @@ export default function RemoteControl() {
const scaleX = 1920 / video.videoWidth; const scaleX = 1920 / video.videoWidth;
const scaleY = 1080 / video.videoHeight; const scaleY = 1080 / video.videoHeight;
socketRef.current?.emit('inputEvent', { // socketRef.current?.emit('inputEvent', {
// targetId: employeeId,
// type: 'mouse',
// action: e.type === 'click' ? 'click' : 'move',
// x: x * scaleX,
// y: y * scaleY,
// button: e.button
// });
console.log(dataChannelRef.current);
dataChannelRef.current.send(JSON.stringify({
targetId: employeeId, targetId: employeeId,
type: 'mouse', type: 'mouse',
action: e.type === 'click' ? 'click' : 'move', action: e.type === 'click' ? 'click' : 'move',
x: x * scaleX, x: x * scaleX,
y: y * scaleY, y: y * scaleY,
button: e.button button: e.button
}); }))
}; };
const handleKeyDown = (e) => { const handleKeyDown = (e) => {
if (!isControlling) return; if (!isControlling) return;
socketRef.current?.emit('inputEvent', { dataChannelRef.current.send(JSON.stringify({
targetId: employeeId, targetId: employeeId,
type: 'keyboard', type: 'keyboard',
key: e.key, key: e.key,
@@ -128,7 +159,16 @@ export default function RemoteControl() {
shiftKey: e.shiftKey, shiftKey: e.shiftKey,
ctrlKey: e.ctrlKey, ctrlKey: e.ctrlKey,
altKey: e.altKey altKey: e.altKey
}); }))
// socketRef.current?.emit('inputEvent', {
// targetId: employeeId,
// type: 'keyboard',
// key: e.key,
// keyCode: e.keyCode,
// shiftKey: e.shiftKey,
// ctrlKey: e.ctrlKey,
// altKey: e.altKey
// });
}; };
return ( return (
@@ -197,45 +237,46 @@ export default function RemoteControl() {
<p style={{ fontSize: '18px', color: 'green' }}> 연결 성공! 화면을 제어할 있습니다.</p> <p style={{ fontSize: '18px', color: 'green' }}> 연결 성공! 화면을 제어할 있습니다.</p>
)} )}
{isControlling && ( <div
tabIndex={0}
style={{
width: '100%',
height: '600px',
border: '2px solid #444',
position: 'relative',
outline: 'none',
backgroundColor: '#000',
display: isControlling ? 'block' : 'none',
}}
onMouseMove={handleMouseEvent}
onClick={handleMouseEvent}
onKeyDown={handleKeyDown}
>
<video
ref={videoRef}
autoPlay
playsInline
muted
controls={false}
controlsList={'nofullscreen'}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
className={'fullscreen'}
/>
<div <div
tabIndex={0}
style={{ style={{
width: '100%', position: 'absolute',
height: '600px', top: '12px',
border: '2px solid #444', left: '12px',
position: 'relative', background: 'rgba(0,0,0,0.7)',
outline: 'none', color: 'white',
backgroundColor: '#000' padding: '6px 12px',
borderRadius: '4px',
fontSize: '16px'
}} }}
onMouseMove={handleMouseEvent}
onClick={handleMouseEvent}
onKeyDown={handleKeyDown}
> >
<video 직원: {employeeId} | 모니터: {selectedDisplay}
ref={videoRef}
autoPlay
playsInline
controls
muted
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/>
<div
style={{
position: 'absolute',
top: '12px',
left: '12px',
background: 'rgba(0,0,0,0.7)',
color: 'white',
padding: '6px 12px',
borderRadius: '4px',
fontSize: '16px'
}}
>
직원: {employeeId} | 모니터: {selectedDisplay}
</div>
</div> </div>
)} </div>
</div> </div>
); );
} }

View File

@@ -12,7 +12,7 @@ function createWindow() {
sandbox: false sandbox: false
} }
}); });
win.webContents.openDevTools(); // win.webContents.openDevTools();
if (app.isPackaged) { if (app.isPackaged) {
win.loadFile(path.join(__dirname, 'frontend/dist/index.html')); win.loadFile(path.join(__dirname, 'frontend/dist/index.html'));