- commit
- 04da858fe8564c85333bc59443e79e4370b28940
- parent
- 460199e9da003a14c6515c6eaa015f75ed926abb
- Author
- Tobias Bengfort <bengfort@mpib-berlin.mpg.de>
- Date
- 2020-03-21 16:16
rtc
Diffstat
M | common.css | 20 | ++++++++++++++++---- |
A | rtc/index.html | 34 | ++++++++++++++++++++++++++++++++++ |
A | rtc/rtc.css | 23 | +++++++++++++++++++++++ |
A | rtc/rtc.js | 168 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 241 insertions, 4 deletions
diff --git a/common.css b/common.css
@@ -8,7 +8,8 @@ body { 8 8 } 9 9 10 10 input,11 -1 button {-1 11 button, -1 12 .btn { 12 13 border: 1px solid #aaa; 13 14 padding: 0.3em 0.75em; 14 15 font-family: inherit; @@ -18,19 +19,30 @@ button { 18 19 min-width: 0; 19 20 } 20 2121 -1 button {-1 22 button, -1 23 .btn { 22 24 display: inline-block; 23 25 text-align: center; 24 26 border-color: #26c; 25 27 background: #26c; 26 28 color: #fff; -1 29 cursor: pointer; 27 30 } 28 31 button:hover,29 -1 button:focus {-1 32 button:focus, -1 33 .btn:hover, -1 34 .btn:focus { 30 35 border-color: #25a; 31 36 background: #25a; 32 37 }33 -1 button:active {-1 38 button:active, -1 39 .btn:active, -1 40 .toggle :checked + .btn { 34 41 border-color: blue; 35 42 background: blue; 36 43 } -1 44 -1 45 .toggle input { -1 46 /* FIXME: hide only visually */ -1 47 display: none; -1 48 }
diff --git a/rtc/index.html b/rtc/index.html
@@ -0,0 +1,34 @@ -1 1 <!DOCTYPE html> -1 2 <html> -1 3 <head> -1 4 <meta charset="utf-8" /> -1 5 <title>duct rtc</title> -1 6 <meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src https://patchbay.pub"> -1 7 <link rel="stylesheet" href="../common.css"> -1 8 <link rel="stylesheet" href="rtc.css"> -1 9 </head> -1 10 <body> -1 11 <div class="rtc"> -1 12 <div class="videos"> -1 13 <video class="local" autoplay muted></video> -1 14 </div> -1 15 <div class="controls"> -1 16 <label class="toggle"> -1 17 <input type="checkbox" name="audio" autocomplete="off"> -1 18 <span class="btn">Audio</span> -1 19 </label> -1 20 <label class="toggle"> -1 21 <input type="checkbox" name="video" autocomplete="off"> -1 22 <span class="btn">Video</span> -1 23 </label> -1 24 <label class="toggle"> -1 25 <input type="checkbox" name="screen" autocomplete="off"> -1 26 <span class="btn">Screen</span> -1 27 </label> -1 28 </div> -1 29 </div> -1 30 <script src="../patch.js"></script> -1 31 <script src="rtc.js"></script> -1 32 </body> -1 33 </html> -1 34
diff --git a/rtc/rtc.css b/rtc/rtc.css
@@ -0,0 +1,23 @@ -1 1 body { -1 2 text-align: center; -1 3 } -1 4 -1 5 .rtc { -1 6 display: grid; -1 7 grid-template-rows: 1fr min-content; -1 8 grid-gap: 1em; -1 9 min-height: calc(100vh - 2em); -1 10 } -1 11 -1 12 .videos { -1 13 display: grid; -1 14 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); -1 15 align-content: center; -1 16 grid-gap: 1em; -1 17 } -1 18 -1 19 video { -1 20 background-color: black; -1 21 width: 100%; -1 22 max-height: 80vh; -1 23 }
diff --git a/rtc/rtc.js b/rtc/rtc.js
@@ -0,0 +1,168 @@ -1 1 (function() { -1 2 // https://webrtc.github.io/samples/ -1 3 // https://www.html5rocks.com/en/tutorials/webrtc/basics/ -1 4 -1 5 // ICE -- networking information -1 6 // offer/answer -- media stream information -1 7 -1 8 var roomUrl = 'https://patchbay.pub/pubsub/' + location.hash.substr(1); -1 9 var queueUrl = 'https://patchbay.pub/queue/' + patch.randomString(10); -1 10 console.log('own queue', queueUrl); -1 11 -1 12 var container = document.querySelector('.videos'); -1 13 var cons = {}; -1 14 -1 15 var localVideo = document.querySelector('video.local'); -1 16 localVideo.srcObject = new MediaStream(); -1 17 -1 18 var getConnection = function(sender) { -1 19 if (sender in cons) { -1 20 return cons[sender].con; -1 21 } -1 22 -1 23 var video = document.createElement('video'); -1 24 video.autoplay = true; -1 25 container.append(video); -1 26 -1 27 var con = new RTCPeerConnection({ -1 28 iceServers: [{urls: 'stun:stun.l.google.com:19302'}] -1 29 }); -1 30 con.addEventListener('icecandidate', function(event) { -1 31 patch.post(sender, {'sender': queueUrl, 'data': event.candidate}); -1 32 }); -1 33 con.addEventListener('track', function(event) { -1 34 // TODO: maybe check if already equal? -1 35 // TODO: rm image if no video stream -1 36 video.srcObject = event.streams[0]; -1 37 }); -1 38 -1 39 var tracks = []; -1 40 localVideo.srcObject.getTracks().forEach(track => { -1 41 var s = con.addTrack(track, localVideo.srcObject); -1 42 tracks.push(s); -1 43 }); -1 44 -1 45 cons[sender] = { -1 46 'con': con, -1 47 'video': video, -1 48 'tracks': tracks, -1 49 }; -1 50 -1 51 return con; -1 52 }; -1 53 -1 54 var makeOffer = function(sender) { -1 55 if (sender !== queueUrl) { -1 56 var con = getConnection(sender); -1 57 con.createOffer().then(offer => { -1 58 con.setLocalDescription(offer).then(_ => { -1 59 patch.post(sender, {'sender': queueUrl, 'data': offer}); -1 60 }); -1 61 }); -1 62 } -1 63 }; -1 64 -1 65 var handleOffer = function(sender, offer) { -1 66 var con = getConnection(sender); -1 67 con.setRemoteDescription(offer).then(_ => { -1 68 con.createAnswer().then(answer => { -1 69 con.setLocalDescription(answer).then(_ => { -1 70 patch.post(sender, {'sender': queueUrl, 'data': answer}); -1 71 }); -1 72 }); -1 73 }); -1 74 }; -1 75 -1 76 var handleAnswer = function(sender, answer) { -1 77 var con = cons[sender].con; -1 78 con.setRemoteDescription(answer); -1 79 }; -1 80 -1 81 var handleCandidate = function(sender, candidate) { -1 82 var con = cons[sender].con; -1 83 con.addIceCandidate(candidate); -1 84 }; -1 85 -1 86 patch.listen(roomUrl, function(data) { -1 87 makeOffer(data); -1 88 }); -1 89 -1 90 patch.listen(queueUrl, function(data) { -1 91 var sender = data.sender; -1 92 var data = data.data; -1 93 if (sender && data) { -1 94 if (data.type === 'offer') { -1 95 return handleOffer(sender, data); -1 96 } else if (data.type === 'answer') { -1 97 return handleAnswer(sender, data); -1 98 } else if (data.candidate) { -1 99 return handleCandidate(sender, data); -1 100 } -1 101 } -1 102 console.log('unknown message', data); -1 103 }); -1 104 -1 105 patch.post(roomUrl, queueUrl); -1 106 -1 107 var updateConnections = function() { -1 108 var sender, c; -1 109 var tracks = localVideo.srcObject.getTracks(); -1 110 -1 111 for (sender in cons) { -1 112 var c = cons[sender]; -1 113 -1 114 while (c.tracks.length) { -1 115 s = c.tracks.pop(); -1 116 c.con.removeTrack(s); -1 117 } -1 118 -1 119 tracks.forEach(track => { -1 120 s = c.con.addTrack(track, localVideo.srcObject); -1 121 c.tracks.push(s); -1 122 }); -1 123 -1 124 makeOffer(sender); -1 125 } -1 126 } -1 127 -1 128 var updateStreams = async function(event) { -1 129 var oldTracks, getStream, other; -1 130 -1 131 if (event.target.name === 'audio') { -1 132 oldTracks = localVideo.srcObject.getAudioTracks(); -1 133 getStream = () => navigator.mediaDevices.getUserMedia({audio: true}); -1 134 } else if (event.target.name === 'video') { -1 135 oldTracks = localVideo.srcObject.getVideoTracks(); -1 136 getStream = () => navigator.mediaDevices.getUserMedia({video: true}); -1 137 other = document.querySelector('[name="screen"]'); -1 138 } else { -1 139 oldTracks = localVideo.srcObject.getVideoTracks(); -1 140 getStream = () => navigator.mediaDevices.getDisplayMedia(); -1 141 other = document.querySelector('[name="video"]'); -1 142 } -1 143 -1 144 oldTracks.forEach(track => { -1 145 track.stop() -1 146 localVideo.srcObject.removeTrack(track); -1 147 }); -1 148 if (event.target.checked) { -1 149 try { -1 150 var stream = await getStream(); -1 151 stream.getTracks().forEach(track => { -1 152 localVideo.srcObject.addTrack(track); -1 153 }); -1 154 } catch { -1 155 event.target.checked = false; -1 156 return; -1 157 } -1 158 } -1 159 -1 160 if (other) { -1 161 other.checked = false; -1 162 } -1 163 -1 164 updateConnections(); -1 165 }; -1 166 -1 167 document.addEventListener('change', updateStreams); -1 168 })();