mirror of
https://github.com/wx-chevalier/screen-sharing-rdp.git
synced 2025-10-09 10:10:14 +08:00
318 lines
7.4 KiB
JavaScript
318 lines
7.4 KiB
JavaScript
let peerConnection = null;
|
|
let dataChannel = null;
|
|
|
|
let resolutionMap = {
|
|
screenWidth: 0,
|
|
screenHeight: 0,
|
|
canvasWidth: 0,
|
|
canvasHeight: 0,
|
|
};
|
|
|
|
function showError(error) {
|
|
const errorNode = document.querySelector("#error");
|
|
if (errorNode.firstChild) {
|
|
errorNode.removeChild(errorNode.firstChild);
|
|
}
|
|
errorNode.appendChild(document.createTextNode(error.message || error));
|
|
}
|
|
|
|
function startSession(offer, screen) {
|
|
return fetch("/api/session", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
offer,
|
|
screen,
|
|
}),
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
})
|
|
.then((res) => {
|
|
return res.json();
|
|
})
|
|
.then((msg) => {
|
|
return msg.answer;
|
|
});
|
|
}
|
|
|
|
function createOffer(pc, { audio, video }) {
|
|
return new Promise((accept, reject) => {
|
|
pc.onicecandidate = (evt) => {
|
|
if (!evt.candidate) {
|
|
// ICE Gathering finished
|
|
const { sdp: offer } = pc.localDescription;
|
|
accept(offer);
|
|
}
|
|
};
|
|
pc.createOffer({
|
|
offerToReceiveAudio: audio,
|
|
offerToReceiveVideo: video,
|
|
})
|
|
.then((ld) => {
|
|
pc.setLocalDescription(ld);
|
|
})
|
|
.catch(reject);
|
|
});
|
|
}
|
|
|
|
function sendDataMessage(command, data) {
|
|
if (dataChannel) {
|
|
// Send cordinates
|
|
dataChannel.send(
|
|
JSON.stringify({
|
|
command: command,
|
|
data: data,
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
function enableMouseEvents(dataChannel) {
|
|
// Start sending mouse cordinates on mouse move in canvas
|
|
const remoteCanvas = document.getElementById("remote-canvas");
|
|
|
|
// On Mouse move
|
|
remoteCanvas.addEventListener("mousemove", (event) => {
|
|
// Get cordinates
|
|
const cordinates = scaleCordinatesToOriginalScreen(event);
|
|
|
|
// Send cordinates
|
|
sendDataMessage("mousemove", {
|
|
x: cordinates.x,
|
|
y: cordinates.y,
|
|
});
|
|
});
|
|
|
|
// On Mouse Click
|
|
remoteCanvas.addEventListener("mousedown", (event) => {
|
|
let button = "left";
|
|
|
|
switch (event.which) {
|
|
case 1:
|
|
button = "left";
|
|
break;
|
|
|
|
case 2:
|
|
button = "center";
|
|
break;
|
|
|
|
case 3:
|
|
button = "right";
|
|
break;
|
|
|
|
default:
|
|
button = "left";
|
|
}
|
|
|
|
sendDataMessage("click", {
|
|
button,
|
|
});
|
|
});
|
|
|
|
// On Mouse Double Click
|
|
remoteCanvas.addEventListener("dblclick", (event) => {
|
|
let button = "left";
|
|
|
|
switch (event.which) {
|
|
case 1:
|
|
button = "left";
|
|
break;
|
|
|
|
case 2:
|
|
button = "center";
|
|
break;
|
|
|
|
case 3:
|
|
button = "right";
|
|
break;
|
|
|
|
default:
|
|
button = "left";
|
|
}
|
|
|
|
sendDataMessage("dblclick", {
|
|
button,
|
|
});
|
|
});
|
|
|
|
// On Mouse Scroll
|
|
remoteCanvas.addEventListener("wheel", (event) => {
|
|
const delta = Math.sign(event.deltaY);
|
|
const direction = delta > 0 ? "down" : "up";
|
|
sendDataMessage("mousescroll", {
|
|
direction,
|
|
});
|
|
});
|
|
|
|
/** DOCUMENT LEVEL EVENT LISTENERS */
|
|
// Read keyboard events
|
|
document.addEventListener("keydown", (event) => {
|
|
sendDataMessage("keydown", {
|
|
keyCode: event.keyCode,
|
|
});
|
|
});
|
|
}
|
|
|
|
function scaleCordinatesToOriginalScreen(event) {
|
|
const remoteCanvas = document.getElementById("remote-canvas");
|
|
// Get canvas size
|
|
const rect = remoteCanvas.getBoundingClientRect();
|
|
// Get mouse cordinates on canvas
|
|
const x = (event.clientX - rect.left).toFixed(0);
|
|
const y = (event.clientY - rect.top).toFixed(0);
|
|
// Calculate screen percentage based on canvas
|
|
const xPer = (x / resolutionMap.canvasWidth) * 100;
|
|
const yPer = (y / resolutionMap.canvasHeight) * 100;
|
|
// Map percentage to original screen
|
|
return {
|
|
x: ((resolutionMap.screenWidth * xPer) / 100).toFixed(0),
|
|
y: ((resolutionMap.screenHeight * yPer) / 100).toFixed(0),
|
|
};
|
|
}
|
|
|
|
function startRemoteSession(screen, remoteVideoNode, stream) {
|
|
let pc;
|
|
|
|
return Promise.resolve()
|
|
.then(() => {
|
|
pc = new RTCPeerConnection({
|
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
|
});
|
|
|
|
dataChannel = pc.createDataChannel("messages");
|
|
|
|
dataChannel.onopen = function (event) {
|
|
enableMouseEvents(dataChannel);
|
|
|
|
// Fetch screen size from server
|
|
sendDataMessage("screensize", {});
|
|
};
|
|
|
|
dataChannel.onmessage = function (event) {
|
|
try {
|
|
const message = JSON.parse(event.data);
|
|
switch (message.command) {
|
|
case "screensize":
|
|
resolutionMap.screenHeight = message.data.height;
|
|
resolutionMap.screenWidth = message.data.width;
|
|
break;
|
|
|
|
case "mousepose":
|
|
console.log(message);
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
};
|
|
|
|
pc.ontrack = (evt) => {
|
|
remoteVideoNode.srcObject = evt.streams[0];
|
|
remoteVideoNode.play();
|
|
};
|
|
|
|
stream &&
|
|
stream.getTracks().forEach((track) => {
|
|
pc.addTrack(track, stream);
|
|
});
|
|
|
|
return createOffer(pc, { audio: false, video: true });
|
|
})
|
|
.then((offer) => {
|
|
return startSession(offer, screen);
|
|
})
|
|
.then((answer) => {
|
|
return pc.setRemoteDescription(
|
|
new RTCSessionDescription({
|
|
sdp: answer,
|
|
type: "answer",
|
|
})
|
|
);
|
|
})
|
|
.then(() => pc);
|
|
}
|
|
|
|
function resizeCanvas(canvas, video) {
|
|
const w = video.offsetWidth;
|
|
const h = video.offsetHeight;
|
|
canvas.width = w;
|
|
canvas.height = h;
|
|
|
|
resolutionMap.canvasHeight = h;
|
|
resolutionMap.canvasWidth = w;
|
|
}
|
|
|
|
function disconnectSession() {
|
|
sendDataMessage("terminate", {});
|
|
peerConnection.close();
|
|
peerConnection = null;
|
|
dataChannel = null;
|
|
enableStartStop(true);
|
|
setStartStopTitle("Connect");
|
|
}
|
|
|
|
const enableStartStop = (enabled) => {
|
|
const startStop = document.querySelector("#start-stop");
|
|
if (enabled) {
|
|
startStop.removeAttribute("disabled");
|
|
} else {
|
|
startStop.setAttribute("disabled", "");
|
|
}
|
|
};
|
|
|
|
const setStartStopTitle = (title) => {
|
|
const startStop = document.querySelector("#start-stop");
|
|
startStop.removeChild(startStop.firstChild);
|
|
startStop.appendChild(document.createTextNode(title));
|
|
};
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
let selectedScreen = 0;
|
|
const remoteVideo = document.querySelector("#remote-video");
|
|
const remoteCanvas = document.querySelector("#remote-canvas");
|
|
// Disable right click context on canvas
|
|
remoteCanvas.oncontextmenu = function (e) {
|
|
e.preventDefault();
|
|
};
|
|
|
|
const startStop = document.querySelector("#start-stop");
|
|
|
|
remoteVideo.onplaying = () => {
|
|
setInterval(() => {
|
|
resizeCanvas(remoteCanvas, remoteVideo);
|
|
}, 1000);
|
|
};
|
|
|
|
startStop.addEventListener("click", () => {
|
|
enableStartStop(false);
|
|
|
|
const userMediaPromise =
|
|
adapter.browserDetails.browser === "safari"
|
|
? navigator.mediaDevices.getUserMedia({ video: true })
|
|
: Promise.resolve(null);
|
|
if (!peerConnection) {
|
|
userMediaPromise.then((stream) => {
|
|
return startRemoteSession(selectedScreen, remoteVideo, stream)
|
|
.then((pc) => {
|
|
remoteVideo.style.setProperty("visibility", "visible");
|
|
peerConnection = pc;
|
|
})
|
|
.catch(showError)
|
|
.then(() => {
|
|
enableStartStop(true);
|
|
setStartStopTitle("Disconnect");
|
|
});
|
|
});
|
|
} else {
|
|
disconnectSession();
|
|
remoteVideo.style.setProperty("visibility", "collapse");
|
|
}
|
|
});
|
|
});
|
|
|
|
window.addEventListener("beforeunload", () => {
|
|
if (peerConnection) {
|
|
peerConnection.close();
|
|
}
|
|
});
|