Skip to content

Instantly share code, notes, and snippets.

@legokichi
Last active March 29, 2024 06:38
Show Gist options
  • Save legokichi/75c74ede4d66590763a32a31208a5551 to your computer and use it in GitHub Desktop.
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/
<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>
@legokichi
Copy link
Author

legokichi commented Mar 25, 2024

v=0
o=- 1467283136327245143 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=extmap-allow-mixed
a=msid-semantic: WMS 8a6f8fe8-224f-421d-bdbc-43e3b69e5abc
m=video 60062 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 133.159.151.108
a=rtcp:9 IN IP4 0.0.0.0
a=candidate:3687080004 1 udp 2122260223 192.168.249.10 60062 typ host generation 0 network-id 1 network-cost 10
a=candidate:164160971 1 udp 1686052607 133.159.151.108 60062 typ srflx raddr 192.168.249.10 rport 60062 generation 0 network-id 1 network-cost 10
a=candidate:2768995036 1 tcp 1518280447 192.168.249.10 9 typ host tcptype active generation 0 network-id 1 network-cost 10
a=ice-ufrag:FNrL
a=ice-pwd:6y0dx96Pi34OuY5eAoyJ0W4K
a=ice-options:trickle
a=fingerprint:sha-256 03:4A:BB:D5:55:ED:41:4A:87:E4:20:21:99:44:4F:FE:02:1A:31:1F:09:64:00:84:7D:4B:32:50:5C:85:59:0E
a=setup:actpass
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=sendrecv
a=msid:8a6f8fe8-224f-421d-bdbc-43e3b69e5abc 00532c6c-fb8b-4f36-ab49-c397dc2144ce
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
a=ssrc-group:FID 4219556918 2885885713
a=ssrc:4219556918 cname:UKtFzKbC8T4ncSXE
a=ssrc:4219556918 msid:8a6f8fe8-224f-421d-bdbc-43e3b69e5abc 00532c6c-fb8b-4f36-ab49-c397dc2144ce
a=ssrc:2885885713 cname:UKtFzKbC8T4ncSXE
a=ssrc:2885885713 msid:8a6f8fe8-224f-421d-bdbc-43e3b69e5abc 00532c6c-fb8b-4f36-ab49-c397dc2144ce

@legokichi
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment