Last active
March 29, 2024 06:38
-
-
Save legokichi/75c74ede4d66590763a32a31208a5551 to your computer and use it in GitHub Desktop.
webrtc peer to peer desktop sharing https://legokichi.github.io/p2p-webrtc-screen-sharing/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
"use strict"; | |
async function captureDesktop() { | |
const localStream = await navigator.mediaDevices.getDisplayMedia({ | |
video: { | |
frameRate: 15, | |
displaySurface: "monitor", | |
}, | |
}); | |
consolelog("make Offer"); | |
const peer = new RTCPeerConnection({ iceServers: [] }); | |
peer.onsignalingstatechange = () => { | |
consolelog("onsignalingstatechange", peer.signalingState); | |
}; | |
peer.onicecandidateerror = (evt) => { | |
consolelog("onicecandidateerror", evt); | |
}; | |
peer.onsignalingstatechange = () => { | |
consolelog("onsignalingstatechange", peer.signalingState); | |
}; | |
peer.oniceconnectionstatechange = () => { | |
consolelog("oniceconnectionstatechange", peer.iceConnectionState); | |
}; | |
peer.onicegatheringstatechange = () => { | |
consolelog("onicegatheringstatechange", peer.iceGatheringState); | |
}; | |
peer.onconnectionstatechange = () => { | |
consolelog("onconnectionstatechange", peer.connectionState); | |
}; | |
peer.ontrack = (event) => { | |
consolelog("ontrack", event); | |
}; | |
peer.onremovestream = (event) => { | |
consolelog("onremovestream", event); | |
}; | |
consolelog("Adding local stream..."); | |
peer.addStream(localStream); | |
const localDescription = await peer.createOffer(); | |
consolelog("createOffer() succsess in promise"); | |
await peer.setLocalDescription(localDescription); | |
consolelog("setLocalDescription() succsess in promise"); | |
await new Promise((resolve) => { | |
peer.onicecandidate = (evt) => { | |
consolelog("onicecandidate", evt.candidate); | |
if (evt.candidate != null) { | |
// Trickle ICE の場合は、ICE candidateを相手に送る | |
// Vanilla ICE の場合には、何もしない | |
} else { | |
// Trickle ICE の場合は、何もしない | |
// Vanilla ICE の場合には、ICE candidateを含んだSDPを相手に送る | |
resolve(); | |
} | |
}; | |
}); | |
const localSdpText = peer.localDescription.sdp | |
consolelog("---sending sdp ---"); | |
// consolelog(localSdpText); | |
const textarea = document.createElement("textarea"); | |
textarea.value = localSdpText; | |
textarea.readonly = true; | |
textarea.onclick = async () => { | |
textarea.focus(); | |
textarea.select(); | |
await navigator.clipboard.writeText(localSdpText); | |
}; | |
document.body.appendChild(textarea); | |
const textarea2 = document.createElement("textarea"); | |
textarea2.placeholder = "paste remote sdp here"; | |
textarea2.onclick = async () => { | |
textarea2.value = await navigator.clipboard.readText(); | |
textarea2.focus(); | |
textarea2.select(); | |
}; | |
document.body.appendChild(textarea2); | |
const button = document.createElement("button"); | |
button.appendChild(document.createTextNode("Receive Offer")); | |
document.body.appendChild(button); | |
await new Promise((resolve) => { button.onclick = resolve; }); | |
let remoteSdpText = textarea2.value; | |
if (remoteSdpText.slice(-1) !== "\n") { | |
remoteSdpText += "\n"; | |
} | |
consolelog("Received answer text..."); | |
consolelog(remoteSdpText); | |
document.body.removeChild(button); | |
document.body.removeChild(textarea); | |
document.body.removeChild(textarea2); | |
const answerSdp = new RTCSessionDescription({ | |
type: "answer", | |
sdp: remoteSdpText, | |
}); | |
await peer.setRemoteDescription(answerSdp); | |
consolelog("setRemoteDescription(answer) succsess in promise"); | |
await new Promise((resolve) => { | |
peer.onconnectionstatechange = () => { | |
consolelog("onconnectionstatechange", peer.iceConnectionState); | |
if (peer.connectionState === "disconnected") { | |
resolve(); | |
} | |
}; | |
}); | |
consolelog("Hang up."); | |
peer.close(); | |
} | |
async function receiveDesktop() { | |
const textarea = document.createElement("textarea"); | |
textarea.placeholder = "paste remote sdp here"; | |
textarea.onclick = async () => { | |
textarea.value = await navigator.clipboard.readText(); | |
textarea.focus(); | |
textarea.select(); | |
}; | |
document.body.appendChild(textarea); | |
const button = document.createElement("button"); | |
button.appendChild(document.createTextNode("Receive Offer")); | |
document.body.appendChild(button); | |
await new Promise((resolve) => { button.onclick = resolve; }); | |
let remoteSdpText = textarea.value; | |
if (remoteSdpText.slice(-1) !== "\n") { | |
remoteSdpText += "\n"; | |
} | |
consolelog("Received offer text..."); | |
// consolelog(remoteSdpText); | |
document.body.removeChild(textarea); | |
document.body.removeChild(button); | |
const offerSdp = new RTCSessionDescription({ | |
type: "offer", | |
sdp: remoteSdpText, | |
}); | |
const peer = new RTCPeerConnection({ iceServers: [] }); | |
peer.onsignalingstatechange = () => { | |
consolelog("onsignalingstatechange", peer.signalingState); | |
}; | |
peer.onicecandidateerror = (evt) => { | |
consolelog("onicecandidateerror", evt); | |
}; | |
peer.oniceconnectionstatechange = () => { | |
consolelog("oniceconnectionstatechange", peer.iceConnectionState); | |
}; | |
peer.onicegatheringstatechange = () => { | |
consolelog("onicegatheringstatechange", peer.iceGatheringState); | |
}; | |
peer.onconnectionstatechange = () => { | |
consolelog("onconnectionstatechange", peer.connectionState); | |
}; | |
const video = document.createElement("video"); | |
peer.ontrack = (event) => { | |
consolelog("ontrack", event); | |
const remoteStream = event.streams[0]; | |
video.muted = true; | |
video.volume = 0; | |
video.autoplay = true; | |
video.playsinline = true; | |
video.controls = false; | |
video.srcObject = remoteStream; | |
}; | |
peer.onremovestream = (event) => { | |
consolelog("onremovestream", event); | |
video.pause(); | |
video.srcObject = null; | |
}; | |
await peer.setRemoteDescription(offerSdp); | |
consolelog("setRemoteDescription(offer) succsess in promise"); | |
consolelog("sending Answer. Creating remote session description..."); | |
const localDescription = await peer.createAnswer(); | |
consolelog("createAnswer() succsess in promise"); | |
await peer.setLocalDescription(localDescription); | |
consolelog("setLocalDescription() succsess in promise"); | |
// -- Trickle ICE の場合は、初期SDPを相手に送る -- | |
// -- Vanilla ICE の場合には、まだSDPは送らない -- | |
//sendSdp(peerConnection.localDescription); | |
await new Promise((resolve) => { | |
peer.onicecandidate = function (evt) { | |
consolelog("onicecandidate", evt.candidate); | |
if (evt.candidate != null) { | |
// Trickle ICE の場合は、ICE candidateを相手に送る | |
// Vanilla ICE の場合には、何もしない | |
} else { | |
// Trickle ICE の場合は、何もしない | |
// Vanilla ICE の場合には、ICE candidateを含んだSDPを相手に送る | |
resolve(); | |
} | |
}; | |
}); | |
const localSdpText = peer.localDescription.sdp; | |
consolelog("---sending sdp ---"); | |
// consolelog(localSdpText); | |
const textarea2 = document.createElement("textarea"); | |
textarea2.value = localSdpText; | |
textarea2.readonly = true; | |
textarea2.onclick = async () => { | |
textarea2.focus(); | |
textarea2.select(); | |
await navigator.clipboard.writeText(localSdpText); | |
}; | |
document.body.appendChild(textarea2); | |
await new Promise((resolve) => { | |
peer.onconnectionstatechange = () => { | |
consolelog("onconnectionstatechange", peer.iceConnectionState); | |
if (peer.connectionState === "connected") { | |
resolve(); | |
} | |
}; | |
}); | |
document.body.removeChild(textarea2); | |
document.body.appendChild(video); | |
video.play(); | |
await new Promise((resolve) => { | |
peer.onconnectionstatechange = () => { | |
consolelog("onconnectionstatechange", peer.iceConnectionState); | |
if (peer.connectionState === "disconnected") { | |
resolve(); | |
} | |
}; | |
}); | |
consolelog("Hang up."); | |
peer.close(); | |
video.pause(); | |
video.srcObject = null; | |
document.body.removeChild(video); | |
} | |
const logarea = document.createElement("textarea"); | |
function consolelog(...args) { | |
console.log(...args); | |
logarea.appendChild(document.createTextNode(args.join(" ") + "\n")); | |
} | |
window.onerror = (message, source, lineno, colno, error) => { | |
consolelog(message, source, lineno, colno, error); | |
}; | |
window.addEventListener("unhandledrejection", (evt) => { | |
consolelog(["onUnhandledrejection", evt.reason.stack].join("\n--------\n")); | |
}); | |
document.addEventListener("DOMContentLoaded", (ev) => { | |
logarea.style = "display: block; width: 100%; height: 200px;"; | |
logarea.readonly = true; | |
// logarea.onclick = async () => { | |
// logarea.focus(); | |
// logarea.select(); | |
// await navigator.clipboard.writeText(logarea.value); | |
// }; | |
document.body.appendChild(logarea); | |
const button1 = document.createElement("button"); | |
const button2 = document.createElement("button"); | |
button1.appendChild(document.createTextNode("captureDesktop")); | |
button2.appendChild(document.createTextNode("receiveDesktop")); | |
button1.onclick = () => { | |
document.body.removeChild(button1); | |
document.body.removeChild(button2); | |
captureDesktop(); | |
}; | |
button2.onclick = () => { | |
document.body.removeChild(button1); | |
document.body.removeChild(button2); | |
receiveDesktop(); | |
}; | |
document.body.appendChild(button1); | |
document.body.appendChild(button2); | |
}); | |
</script> | |
<body> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
v=0
o=- 2894475170694576761 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=extmap-allow-mixed
a=msid-semantic: WMS
m=video 42863 UDP/TLS/RTP/SAVPF 96 97 102 103 104 105 106 107 108 109 127 125 39 40 45 46 98 99 100 101 112 113 114
c=IN IP4 192.168.249.54
a=rtcp:9 IN IP4 0.0.0.0
a=candidate:117327436 1 udp 2113937151 192.168.249.54 42863 typ host generation 0 network-cost 999
a=ice-ufrag:TpRA
a=ice-pwd:/EFJHqGJTil8s7ErV8SFvkyW
a=ice-options:trickle
a=fingerprint:sha-256 44:F9:C0:E4:79:4F:63:59:8A:41:34:33:AB:EB:69:BE:7A:5E:F9:37:22:E5:8E:E7:7F:89:8B:AA:6E:7D:83:B9
a=setup:active
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 urn:3gpp:video-orientation
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:103 rtx/90000
a=fmtp:103 apt=102
a=rtpmap:104 H264/90000
a=rtcp-fb:104 goog-remb
a=rtcp-fb:104 transport-cc
a=rtcp-fb:104 ccm fir
a=rtcp-fb:104 nack
a=rtcp-fb:104 nack pli
a=fmtp:104 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:105 rtx/90000
a=fmtp:105 apt=104
a=rtpmap:106 H264/90000
a=rtcp-fb:106 goog-remb
a=rtcp-fb:106 transport-cc
a=rtcp-fb:106 ccm fir
a=rtcp-fb:106 nack
a=rtcp-fb:106 nack pli
a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=106
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
a=rtpmap:125 rtx/90000
a=fmtp:125 apt=127
a=rtpmap:39 H264/90000
a=rtcp-fb:39 goog-remb
a=rtcp-fb:39 transport-cc
a=rtcp-fb:39 ccm fir
a=rtcp-fb:39 nack
a=rtcp-fb:39 nack pli
a=fmtp:39 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f
a=rtpmap:40 rtx/90000
a=fmtp:40 apt=39
a=rtpmap:45 AV1/90000
a=rtcp-fb:45 goog-remb
a=rtcp-fb:45 transport-cc
a=rtcp-fb:45 ccm fir
a=rtcp-fb:45 nack
a=rtcp-fb:45 nack pli
a=rtpmap:46 rtx/90000
a=fmtp:46 apt=45
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:112 red/90000
a=rtpmap:113 rtx/90000
a=fmtp:113 apt=112
a=rtpmap:114 ulpfec/90000