@@ -1,5 +1,33 @@ | |||
const connections = {} | |||
const datachannels = {} | |||
const streams = {} | |||
const screen = {} | |||
function setTracks({source, tracks}) { | |||
streams[source] = streams[source] || new MediaStream() | |||
streams[source].getTracks().forEach(t => streams[source].removeTrack(t)) | |||
tracks.forEach(track => streams[source].addTrack(track)) | |||
m.redraw() | |||
if(source === State.username) { | |||
reloadAllStreams() | |||
} | |||
} | |||
function reloadAllStreams() { | |||
State.online.forEach(username => { | |||
signal({kind: 'rpc', value: {type: 'request'}, source: username}) | |||
}) | |||
} | |||
function getOwnTracks() { | |||
if(streams[State.username]) { | |||
return streams[State.username].getTracks() | |||
} | |||
else { | |||
return [] | |||
} | |||
} | |||
function createConnection(target) { | |||
const rpc = new RTCPeerConnection(rpcConfig) | |||
@@ -10,8 +38,9 @@ function createConnection(target) { | |||
wire({kind: 'rpc', value, target}) | |||
} | |||
} | |||
rpc.ontrack = ({track}) => { | |||
console.log(track) | |||
rpc.ontrack = () => { | |||
const tracks = rpc.getReceivers().map(t => t.track) | |||
setTracks({source: target, tracks: tracks}) | |||
} | |||
rpc.onconnectionstatechange = () => { | |||
if(rpc.connectionState === 'failed') { | |||
@@ -39,9 +68,16 @@ function createConnection(target) { | |||
async function handlePeerInfo({source: target, value}) { | |||
const rpc = connections[target] | |||
if(!rpc) { | |||
return | |||
} | |||
const olds = rpc.getSenders().map(t => t.track) | |||
const news = getOwnTracks() | |||
rpc.getSenders().filter(s => !news.includes(s.track)).forEach(s => rpc.removeTrack(s)) | |||
news.filter(track => !olds.includes(track)).forEach(t => t && rpc.addTrack(t)) | |||
if(value.type === 'request') { | |||
const localOffer = await rpc.createOffer() | |||
await rpc.setLocalDescription(localOffer) | |||
@@ -56,21 +92,32 @@ async function handlePeerInfo({source: target, value}) { | |||
} | |||
else if(value.type === 'answer') { | |||
const remoteAnswer = new RTCSessionDescription(value) | |||
await rpc.setRemoteDescription(remoteAnswer) | |||
await rpc.setRemoteDescription(remoteAnswer).catch(e => e) | |||
} | |||
else if(value.type === 'candidate') { | |||
const candidate = new RTCIceCandidate(value.candidate) | |||
await rpc.addIceCandidate(candidate) | |||
await rpc.addIceCandidate(candidate).catch(e => e) | |||
} | |||
} | |||
function removeConnection(user) { | |||
const rpc = connections[user] | |||
if(rpc) { | |||
delete connections[user] | |||
function destroyConnection(username) { | |||
if(streams[username]) { | |||
streams[username].getTracks().forEach(t => t.stop()) | |||
delete streams[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] | |||
} | |||
} | |||
addEventListener('tracks', (e) => setTracks(e.detail.value)) | |||
addEventListener('rpc', (e) => handlePeerInfo(e.detail)) | |||
addEventListener('join', (e) => createConnection(e.detail.value)) | |||
addEventListener('leave', (e) => destroyConnection(e.detail.value)) | |||
addEventListener('load', () => doNotLog.add('rpc')) |
@@ -2,7 +2,7 @@ const Toggle = { | |||
view({attrs: {label, value}}) { | |||
const onclick = () => { | |||
StreamContainer[value] = !StreamContainer[value] | |||
updateSelfVideo() | |||
requestStream() | |||
} | |||
const checked = StreamContainer[value] | |||
return m('label.styled', | |||
@@ -29,67 +29,21 @@ const VideoConfig = { | |||
] | |||
} | |||
} | |||
const updateSelfVideo = async () => { | |||
const video = document.querySelector('video.self') | |||
video.srcObject.getTracks().forEach(track => { | |||
track.stop() | |||
video.srcObject.removeTrack(track) | |||
delete track | |||
}) | |||
const requestStream = async () => { | |||
if(StreamContainer.videoOn || StreamContainer.audioOn) { | |||
await navigator.mediaDevices | |||
.getUserMedia(VideoConfig) | |||
.then(s => s.getTracks().forEach(t => video.srcObject.addTrack(t))) | |||
.then(s => signal({kind: 'tracks', value: {source: State.username, tracks: s.getTracks()}})) | |||
.catch(e => console.error(e)) | |||
} | |||
video.srcObject = video.srcObject // safari | |||
wire({kind: 'peerInfo', value: {type: 'request'}}) | |||
} | |||
const updateOtherVideo = (target, dom) => { | |||
dom.srcObject = new MediaStream() | |||
let rpc = null | |||
const stopRpc = () => { | |||
rpc && rpc.close() | |||
dom.srcObject.getTracks().forEach(track => { | |||
track.stop() | |||
dom.srcObject.removeTrack(track) | |||
delete track | |||
}) | |||
} | |||
const resetRpc = () => { | |||
stopRpc() | |||
rpc = new RTCPeerConnection(rpcConfig) | |||
document.querySelector('video.self').srcObject.getTracks() | |||
.forEach(t => rpc.addTrack(t)) | |||
} | |||
} | |||
const Video = { | |||
setUp: (username) => ({dom}) => { | |||
dom.username = username | |||
dom.srcObject = new MediaStream() | |||
if(username === State.username) { | |||
dom.classList.add('self') | |||
dom.muted = true | |||
updateSelfVideo() | |||
} | |||
else { | |||
updateOtherVideo(username, dom) | |||
requestStream() | |||
} | |||
}, | |||
tearDown: (username) => ({dom}) => { | |||
removeEventListener('peerInfo', dom.listener) | |||
dom.srcObject.getTracks().forEach(track => { | |||
track.stop() | |||
dom.srcObject.removeTrack(track) | |||
delete track | |||
}) | |||
}, | |||
view({attrs: {username}}) { | |||
const styleOuter = { | |||
position: 'relative', | |||
@@ -119,8 +73,8 @@ const Video = { | |||
), | |||
m('video[playsinline][autoplay]', { | |||
style: styleVideo, | |||
srcObject: streams[username], | |||
oncreate: this.setUp(username), | |||
onremove: this.tearDown(username), | |||
}), | |||
) | |||
}, | |||
@@ -148,11 +102,6 @@ const StreamContainer = { | |||
) | |||
}, | |||
} | |||
const signalPeerStop = (username) => { | |||
signal({kind: 'peerInfo', value: {type: 'stop'}, source: username}) | |||
} | |||
addEventListener('pagehide', () => State.online.forEach(signalPeerStop)) | |||
addEventListener('logout', () => State.online.forEach(signalPeerStop)) | |||
addEventListener('load', () => { | |||
Headers.push([VideoConfig]) | |||
Apps.push([StreamContainer, {key: 'stream-container'}]) |
@@ -9,11 +9,10 @@ | |||
<!-- <script src="/libs/purify.min.js"></script> --> | |||
<script src="/pico.js" defer></script> | |||
<script src="/apps/rpc.js"></script> | |||
<!-- <script src="/apps/streams.js"></script> --> | |||
<script src="/apps/streams.js"></script> | |||
<!-- <script src="/apps/screen.js"></script> --> | |||
<!-- <script src="/apps/chat.js"></script> --> | |||
<!-- <script src="/apps/volume.js"></script> --> | |||
<!-- <script src="/apps/screen.js"></script> --> | |||
</head> | |||
<style> | |||
body { |