const connections = {} const datachannels = {} const videoElements = {} function setStream({source, stream}) { const element = videoElements[source] const active = element && element.srcObject if(active && active.id !== stream.id) { videoElements[source].srcObject.getTracks().forEach(tr => tr.stop()) } element.srcObject = stream if(source === State.username) { signal({kind: 'stream-refresh'}) } } function reloadAllStreams() { State.online.forEach(username => { const rpc = connections[username] if(rpc) { rpc.getSenders().map(s => console.log(s) || rpc.removeTrack(s)) } signal({kind: 'rpc', value: {type: 'request'}, source: username}) }) } function createConnection(target) { const rpc = new RTCPeerConnection(rpcConfig) rpc.onicecandidate = ({candidate}) => { if(candidate && candidate.candidate) { const value = {type: 'candidate', candidate} wire({kind: 'rpc', value, target}) } } rpc.ontrack = ({streams: [stream]}) => { setStream({source: target, stream}) } rpc.onconnectionstatechange = () => { if(rpc.connectionState === 'failed') { console.log(target, 'failed, retry!') wire({kind: 'rpc', value: {type: 'request'}, target}) } } rpc.ondatachannel = ({channel}) => { datachannels[target] = channel datachannels[target].onmessage = ({data}) => console.log(data) // for testing purposes const msg = `rpc established from ${target} to ${State.username}` datachannels[target].send(msg) console.log(msg) } rpc.onnegotiationneeded = () => { console.log('lmao renegotiate bi') } connections[target] = rpc if(State.username > target) { datachannels[target] = rpc.createDataChannel('test') datachannels[target].onmessage = ({data}) => console.log(data) signal({kind: 'rpc', value: {type: 'request'}, source: target}) } } function setSenders(rpc, username) { const element = videoElements[username] const stream = element && element.srcObject rpc.getSenders().forEach(se => rpc.removeTrack(se)) if(stream) { stream.getTracks().forEach(tr => rpc.addTrack(tr, stream)) } } async function handlePeerInfo({source: target, value, kind}) { const rpc = connections[target] if(!rpc) { return } if(value.type === 'request') { const localOffer = await rpc.createOffer() await rpc.setLocalDescription(localOffer) wire({kind, value: localOffer, target}) } else if(value.type === 'offer') { const remoteOffer = new RTCSessionDescription(value) await rpc.setRemoteDescription(remoteOffer) const localAnswer = await rpc.createAnswer() await rpc.setLocalDescription(localAnswer) wire({kind, value: localAnswer, target}) } else if(value.type === 'answer') { const remoteAnswer = new RTCSessionDescription(value) await rpc.setRemoteDescription(remoteAnswer).catch(e => e) } else if(value.type === 'candidate') { const candidate = new RTCIceCandidate(value.candidate) await rpc.addIceCandidate(candidate).catch(e => e) } } function destroyConnection(username) { if(datachannels[username]) { datachannels[username].close() delete datachannels[username] } if(connections[username]) { connections[username].getReceivers().forEach(r => r.track.stop()) connections[username].close() delete connections[username] } } function destroyAll() { const people = new Set() const collect = [connections, datachannels, streams, screen] .map(collection => Object.keys(collection)) .forEach(keys => keys.forEach(key => people.add(key))) people.forEach(destroyConnection) } addEventListener('stream-refresh', reloadAllStreams) addEventListener('stream', (e) => setStream(e.detail.value)) addEventListener('rpc', (e) => handlePeerInfo(e.detail)) addEventListener('join', (e) => createConnection(e.detail.value)) addEventListener('leave', (e) => destroyConnection(e.detail.value)) addEventListener('logout', () => destroyAll()) addEventListener('load', () => doNotLog.add('rpc'))