update
This commit is contained in:
386
APP/screen-share/renderer.js
Normal file
386
APP/screen-share/renderer.js
Normal file
@@ -0,0 +1,386 @@
|
||||
// ─── Elemente ───
|
||||
const serverUrlInput = document.getElementById('serverUrl');
|
||||
const myIdInput = document.getElementById('myId');
|
||||
const remoteIdInput = document.getElementById('remoteId');
|
||||
const btnRegister = document.getElementById('btnRegister');
|
||||
const btnShare = document.getElementById('btnShare');
|
||||
const btnControl = document.getElementById('btnControl');
|
||||
const btnStop = document.getElementById('btnStop');
|
||||
const localVideo = document.getElementById('localVideo');
|
||||
const remoteVideo = document.getElementById('remoteVideo');
|
||||
const localPlaceholder = document.getElementById('localPlaceholder');
|
||||
const remotePlaceholder = document.getElementById('remotePlaceholder');
|
||||
const statusDot = document.getElementById('statusDot');
|
||||
const statusText = document.getElementById('statusText');
|
||||
const peerInfo = document.getElementById('peerInfo');
|
||||
const remotePanel = document.getElementById('remotePanel');
|
||||
const logEl = document.getElementById('log');
|
||||
|
||||
// ─── State ───
|
||||
let ws = null;
|
||||
let peerConnection = null;
|
||||
let dataChannel = null;
|
||||
let localStream = null;
|
||||
let controlActive = false;
|
||||
let screenWidth = window.screen.width;
|
||||
let screenHeight = window.screen.height;
|
||||
|
||||
myIdInput.value = 'PC-' + Math.random().toString(36).substr(2, 4).toUpperCase();
|
||||
|
||||
const ICE_SERVERS = [
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' },
|
||||
{ urls: 'stun:stun2.l.google.com:19302' },
|
||||
];
|
||||
|
||||
// ─── Logging ───
|
||||
function log(msg, type = '') {
|
||||
const div = document.createElement('div');
|
||||
div.className = type;
|
||||
div.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
|
||||
logEl.appendChild(div);
|
||||
logEl.scrollTop = logEl.scrollHeight;
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
// ─── WebSocket Signaling ───
|
||||
btnRegister.addEventListener('click', () => {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const url = serverUrlInput.value.trim();
|
||||
ws = new WebSocket(url);
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.send(JSON.stringify({ type: 'register', peerId: myIdInput.value }));
|
||||
statusDot.className = 'status-dot online';
|
||||
statusText.textContent = 'Verbunden als ' + myIdInput.value;
|
||||
btnRegister.textContent = 'Trennen';
|
||||
log('Mit Signal-Server verbunden', 'info');
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
statusDot.className = 'status-dot offline';
|
||||
statusText.textContent = 'Nicht verbunden';
|
||||
btnRegister.textContent = 'Verbinden';
|
||||
log('Signal-Server getrennt');
|
||||
};
|
||||
|
||||
ws.onerror = (err) => {
|
||||
log('WebSocket-Fehler: ' + err.message, 'error');
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
handleSignal(msg);
|
||||
};
|
||||
});
|
||||
|
||||
async function handleSignal(msg) {
|
||||
switch (msg.type) {
|
||||
case 'registered':
|
||||
log('Registriert als: ' + msg.peerId, 'info');
|
||||
break;
|
||||
|
||||
case 'offer':
|
||||
log('Offer empfangen von ' + msg.from, 'info');
|
||||
peerInfo.textContent = 'Verbunden mit: ' + msg.from;
|
||||
await handleOffer(msg);
|
||||
break;
|
||||
|
||||
case 'answer':
|
||||
log('Answer empfangen von ' + msg.from, 'info');
|
||||
if (peerConnection) {
|
||||
await peerConnection.setRemoteDescription(new RTCSessionDescription(msg.sdp));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ice-candidate':
|
||||
if (peerConnection && msg.candidate) {
|
||||
await peerConnection.addIceCandidate(new RTCIceCandidate(msg.candidate));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'control':
|
||||
if (window.electronAPI) {
|
||||
window.electronAPI.sendControl(msg.data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── WebRTC ───
|
||||
function createPeerConnection() {
|
||||
if (peerConnection) {
|
||||
peerConnection.close();
|
||||
}
|
||||
|
||||
peerConnection = new RTCPeerConnection({ iceServers: ICE_SERVERS });
|
||||
|
||||
peerConnection.onicecandidate = (event) => {
|
||||
if (event.candidate) {
|
||||
sendSignal({
|
||||
type: 'ice-candidate',
|
||||
target: remoteIdInput.value,
|
||||
candidate: event.candidate,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
peerConnection.ontrack = (event) => {
|
||||
remoteVideo.srcObject = event.streams[0];
|
||||
remotePlaceholder.style.display = 'none';
|
||||
log('Remote-Stream empfangen', 'info');
|
||||
};
|
||||
|
||||
peerConnection.ondatachannel = (event) => {
|
||||
dataChannel = event.channel;
|
||||
setupDataChannel(dataChannel);
|
||||
log('DataChannel empfangen (Fernsteuerung bereit)', 'info');
|
||||
};
|
||||
|
||||
peerConnection.oniceconnectionstatechange = () => {
|
||||
log('ICE Status: ' + peerConnection.iceConnectionState);
|
||||
if (peerConnection.iceConnectionState === 'disconnected' ||
|
||||
peerConnection.iceConnectionState === 'failed') {
|
||||
log('Verbindung verloren!', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
return peerConnection;
|
||||
}
|
||||
|
||||
function setupDataChannel(dc) {
|
||||
dc.onopen = () => log('DataChannel offen – Fernsteuerung aktiv', 'info');
|
||||
dc.onclose = () => log('DataChannel geschlossen');
|
||||
dc.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (window.electronAPI) {
|
||||
window.electronAPI.sendControl(data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function sendSignal(msg) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(msg));
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Bildschirm teilen ───
|
||||
btnShare.addEventListener('click', async () => {
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
||||
log('Zuerst mit Signal-Server verbinden!', 'error');
|
||||
return;
|
||||
}
|
||||
if (!remoteIdInput.value.trim()) {
|
||||
log('Remote-ID eingeben!', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const sources = await window.electronAPI.getSources();
|
||||
if (sources.length === 0) {
|
||||
log('Keine Bildschirmquellen gefunden', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
localStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: false,
|
||||
video: {
|
||||
mandatory: {
|
||||
chromeMediaSource: 'desktop',
|
||||
chromeMediaSourceId: sources[0].id,
|
||||
maxWidth: 1920,
|
||||
maxHeight: 1080,
|
||||
maxFrameRate: 30,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
localVideo.srcObject = localStream;
|
||||
localPlaceholder.style.display = 'none';
|
||||
log('Bildschirm wird erfasst: ' + sources[0].name, 'info');
|
||||
|
||||
const pc = createPeerConnection();
|
||||
|
||||
dataChannel = pc.createDataChannel('control');
|
||||
setupDataChannel(dataChannel);
|
||||
|
||||
localStream.getTracks().forEach((track) => {
|
||||
pc.addTrack(track, localStream);
|
||||
});
|
||||
|
||||
const offer = await pc.createOffer();
|
||||
await pc.setLocalDescription(offer);
|
||||
|
||||
sendSignal({
|
||||
type: 'offer',
|
||||
target: remoteIdInput.value,
|
||||
sdp: offer,
|
||||
screenWidth,
|
||||
screenHeight,
|
||||
});
|
||||
|
||||
log('Offer gesendet an ' + remoteIdInput.value, 'info');
|
||||
peerInfo.textContent = 'Verbinde mit: ' + remoteIdInput.value;
|
||||
} catch (err) {
|
||||
log('Fehler: ' + err.message, 'error');
|
||||
}
|
||||
});
|
||||
|
||||
async function handleOffer(msg) {
|
||||
const pc = createPeerConnection();
|
||||
|
||||
await pc.setRemoteDescription(new RTCSessionDescription(msg.sdp));
|
||||
|
||||
if (msg.screenWidth) screenWidth = msg.screenWidth;
|
||||
if (msg.screenHeight) screenHeight = msg.screenHeight;
|
||||
|
||||
const answer = await pc.createAnswer();
|
||||
await pc.setLocalDescription(answer);
|
||||
|
||||
sendSignal({
|
||||
type: 'answer',
|
||||
target: msg.from,
|
||||
sdp: answer,
|
||||
});
|
||||
|
||||
log('Answer gesendet an ' + msg.from, 'info');
|
||||
}
|
||||
|
||||
// ─── Fernsteuerung (Maus/Tastatur über Remote-Video senden) ───
|
||||
btnControl.addEventListener('click', () => {
|
||||
controlActive = !controlActive;
|
||||
btnControl.textContent = controlActive ? 'Steuerung AN' : 'Fernsteuern';
|
||||
btnControl.className = controlActive ? 'btn-danger' : 'btn-secondary';
|
||||
log(controlActive ? 'Fernsteuerung AKTIVIERT' : 'Fernsteuerung DEAKTIVIERT', 'info');
|
||||
});
|
||||
|
||||
function sendControlData(data) {
|
||||
if (!controlActive) return;
|
||||
|
||||
if (dataChannel && dataChannel.readyState === 'open') {
|
||||
dataChannel.send(JSON.stringify(data));
|
||||
} else {
|
||||
sendSignal({
|
||||
type: 'control',
|
||||
target: remoteIdInput.value,
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getRelativeCoords(e) {
|
||||
const rect = remoteVideo.getBoundingClientRect();
|
||||
const videoWidth = remoteVideo.videoWidth || 1920;
|
||||
const videoHeight = remoteVideo.videoHeight || 1080;
|
||||
|
||||
const scaleX = screenWidth / rect.width;
|
||||
const scaleY = screenHeight / rect.height;
|
||||
|
||||
return {
|
||||
x: (e.clientX - rect.left) * scaleX,
|
||||
y: (e.clientY - rect.top) * scaleY,
|
||||
};
|
||||
}
|
||||
|
||||
remotePanel.addEventListener('mousemove', (e) => {
|
||||
const coords = getRelativeCoords(e);
|
||||
sendControlData({ action: 'mousemove', ...coords });
|
||||
});
|
||||
|
||||
remotePanel.addEventListener('mousedown', (e) => {
|
||||
e.preventDefault();
|
||||
const btn = ['left', 'middle', 'right'][e.button] || 'left';
|
||||
const coords = getRelativeCoords(e);
|
||||
sendControlData({ action: 'mousedown', button: btn, ...coords });
|
||||
});
|
||||
|
||||
remotePanel.addEventListener('mouseup', (e) => {
|
||||
e.preventDefault();
|
||||
const btn = ['left', 'middle', 'right'][e.button] || 'left';
|
||||
sendControlData({ action: 'mouseup', button: btn });
|
||||
});
|
||||
|
||||
remotePanel.addEventListener('contextmenu', (e) => e.preventDefault());
|
||||
|
||||
remotePanel.addEventListener('wheel', (e) => {
|
||||
if (!controlActive) return;
|
||||
e.preventDefault();
|
||||
sendControlData({
|
||||
action: 'scroll',
|
||||
deltaX: Math.sign(e.deltaX) * 3,
|
||||
deltaY: Math.sign(e.deltaY) * 3,
|
||||
});
|
||||
}, { passive: false });
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!controlActive) return;
|
||||
if (e.target.tagName === 'INPUT') return;
|
||||
e.preventDefault();
|
||||
|
||||
const modifiers = [];
|
||||
if (e.ctrlKey) modifiers.push('control');
|
||||
if (e.shiftKey) modifiers.push('shift');
|
||||
if (e.altKey) modifiers.push('alt');
|
||||
|
||||
sendControlData({
|
||||
action: 'keydown',
|
||||
key: mapKey(e.key),
|
||||
modifiers,
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (!controlActive) return;
|
||||
if (e.target.tagName === 'INPUT') return;
|
||||
e.preventDefault();
|
||||
|
||||
sendControlData({
|
||||
action: 'keyup',
|
||||
key: mapKey(e.key),
|
||||
modifiers: [],
|
||||
});
|
||||
});
|
||||
|
||||
function mapKey(key) {
|
||||
const keyMap = {
|
||||
'Enter': 'enter', 'Backspace': 'backspace', 'Delete': 'delete',
|
||||
'Tab': 'tab', 'Escape': 'escape', ' ': 'space',
|
||||
'ArrowUp': 'up', 'ArrowDown': 'down', 'ArrowLeft': 'left', 'ArrowRight': 'right',
|
||||
'Home': 'home', 'End': 'end', 'PageUp': 'pageup', 'PageDown': 'pagedown',
|
||||
'Control': 'control', 'Shift': 'shift', 'Alt': 'alt',
|
||||
'F1': 'f1', 'F2': 'f2', 'F3': 'f3', 'F4': 'f4', 'F5': 'f5',
|
||||
'F6': 'f6', 'F7': 'f7', 'F8': 'f8', 'F9': 'f9', 'F10': 'f10',
|
||||
'F11': 'f11', 'F12': 'f12',
|
||||
};
|
||||
return keyMap[key] || key.toLowerCase();
|
||||
}
|
||||
|
||||
// ─── Stopp ───
|
||||
btnStop.addEventListener('click', () => {
|
||||
if (localStream) {
|
||||
localStream.getTracks().forEach((t) => t.stop());
|
||||
localStream = null;
|
||||
localVideo.srcObject = null;
|
||||
localPlaceholder.style.display = '';
|
||||
}
|
||||
if (peerConnection) {
|
||||
peerConnection.close();
|
||||
peerConnection = null;
|
||||
}
|
||||
if (dataChannel) {
|
||||
dataChannel.close();
|
||||
dataChannel = null;
|
||||
}
|
||||
remoteVideo.srcObject = null;
|
||||
remotePlaceholder.style.display = '';
|
||||
controlActive = false;
|
||||
btnControl.textContent = 'Fernsteuern';
|
||||
btnControl.className = 'btn-secondary';
|
||||
peerInfo.textContent = '';
|
||||
log('Alle Streams gestoppt', 'info');
|
||||
});
|
||||
Reference in New Issue
Block a user