const displayMediaOptions = { video: { displaySurface: "browser", }, audio: { suppressLocalAudioPlayback: false, }, preferCurrentTab: false, selfBrowserSurface: "exclude", systemAudio: "include", surfaceSwitching: "include", monitorTypeSurfaces: "include", }; const peerConnection = new RTCPeerConnection(); const signaling = Arcast.getBroadcastingChannel( "screen-sharing", onSignalReceived ); var secret = window.crypto.randomUUID(); var receivedAnswer = false; var receivedOffer = false; var isOffering = false; peerConnection.onconnectionstatechange = (evt) => { console.log("Connection state changed", evt); }; peerConnection.oniceconnectionstatechange = () => { console.log("ICE state: ", peerConnection.iceConnectionState); }; peerConnection.onicecandidate = (evt) => { signaling.send( JSON.stringify({ type: "icecandidate", data: { candidate: evt.candidate, }, }) ); }; peerConnection.ontrack = function (e) { var screenplay = document.getElementById("screenplay"); if (!screenplay) { screenplay = document.createElement("video"); screenplay.id = "screenplay"; document.body.appendChild(screenplay); } screenplay.autoplay = true; screenplay.playsInline = true; screenplay.srcObject = e.streams[0]; screenplay.muted = true; e.track.onended = (e) => (screenplay.srcObject = screenplay.srcObject); }; function main() { const urlParams = new URLSearchParams(window.location.search); if (urlParams.has("secret")) { secret = urlParams.get("secret"); } } function shareScreen() { return Arcast.getInfo().then((info) => { return navigator.mediaDevices .getDisplayMedia(displayMediaOptions) .then((captureStream) => { isOffering = true; const videoTrack = captureStream.getVideoTracks()[0]; peerConnection.addTrack(videoTrack, captureStream); peerConnection.createOffer().then((offer) => { peerConnection.setLocalDescription(offer).then(() => { const intervalId = setInterval(() => { if (receivedAnswer) { clearInterval(intervalId); return; } signaling.send( JSON.stringify({ type: "offer", data: { secret: secret, offer: offer, }, }) ); }, 1000); const url = "http://127.0.0.1:" + info.port + "/apps/screen-sharing/?secret=" + encodeURIComponent(secret); fetch("/api/v1/cast", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ url: url, }), }); }); }); }); }); } function onSignalReceived(data) { const message = JSON.parse(data); switch (message.type) { case "offer": if (message.data.secret !== secret) { return; } if (receivedOffer || isOffering) { return; } peerConnection.setRemoteDescription( new RTCSessionDescription(message.data.offer), () => { peerConnection.createAnswer(function (answer) { peerConnection.setLocalDescription( answer, function () { signaling.send( JSON.stringify({ type: "answer", data: { secret: secret, answer: answer, }, }) ); }, error ); }, error); }, error ); receivedOffer = true; break; case "answer": if (receivedAnswer || !isOffering) { return; } if (message.data.secret !== secret) { return; } peerConnection.setRemoteDescription( new RTCSessionDescription(message.data.answer), () => {}, error ); receivedAnswer = true; break; case "icecandidate": if (message.data.candidate) { peerConnection.addIceCandidate(message.data.candidate); } break; default: console.log("Received unhandled message", message); } } function endCall() { var videos = document.getElementsByTagName("video"); for (var i = 0; i < videos.length; i++) { videos[i].pause(); } peerConnection.close(); } function error(err) { console.error(err); endCall(); } main();