@@ -0,0 +1,76 @@ | |||
const connections = {} | |||
const datachannels = {} | |||
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 = ({track}) => { | |||
console.log(track) | |||
} | |||
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) | |||
} | |||
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}) | |||
} | |||
} | |||
async function handlePeerInfo({source: target, value}) { | |||
const rpc = connections[target] | |||
if(!rpc) { | |||
return | |||
} | |||
if(value.type === 'request') { | |||
const localOffer = await rpc.createOffer() | |||
await rpc.setLocalDescription(localOffer) | |||
wire({kind: 'rpc', 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: 'rpc', value: localAnswer, target}) | |||
} | |||
else if(value.type === 'answer') { | |||
const remoteAnswer = new RTCSessionDescription(value) | |||
await rpc.setRemoteDescription(remoteAnswer) | |||
} | |||
else if(value.type === 'candidate') { | |||
const candidate = new RTCIceCandidate(value.candidate) | |||
await rpc.addIceCandidate(candidate) | |||
} | |||
} | |||
function removeConnection(user) { | |||
const rpc = connections[user] | |||
if(rpc) { | |||
delete connections[user] | |||
} | |||
} | |||
addEventListener('rpc', (e) => handlePeerInfo(e.detail)) | |||
addEventListener('join', (e) => createConnection(e.detail.value)) | |||
addEventListener('load', () => doNotLog.add('rpc')) |
@@ -1,7 +1,15 @@ | |||
const ScreenShareConfig = { | |||
toggle() { | |||
if(ScreenShare.isOn && ScreenShare.isStreaming) { | |||
wire({kind: 'screen-sharing-stop'}) | |||
} | |||
else { | |||
ScreenShare.requestScreen() | |||
} | |||
}, | |||
view() { | |||
const checked = ScreenShare.isOn | |||
const onclick = ScreenShare.toggle | |||
const onclick = ScreenShareConfig.toggle | |||
return m('label.styled', | |||
m('input[type=checkbox]', {checked, onclick}), | |||
'screen-share', | |||
@@ -9,31 +17,46 @@ const ScreenShareConfig = { | |||
} | |||
} | |||
const ScreenShare = { | |||
isOn: false, | |||
start() { | |||
const screen = document.querySelector('video.screen') | |||
if(screen) { | |||
navigator.mediaDevices.getDisplayMedia() | |||
.then(s => {screen.srcObject = s}) | |||
.catch(e => console.error(e)) | |||
} | |||
streamer: null, | |||
stream: null, | |||
get isStreaming() { | |||
return ScreenShare.streamer === State.username | |||
}, | |||
toggle() { | |||
ScreenShare.isOn = !ScreenShare.isOn | |||
if(ScreenShare.isOn) { | |||
// setTimeout(ScreenShare.start, 100) | |||
} | |||
get isOn() { | |||
return !! ScreenShare.streamer | |||
}, | |||
async requestScreen() { | |||
// ScreenShare.stream = await navigator.mediaDevices.getDisplayMedia() | |||
// ScreenShare.streamer = State.username | |||
// wire({kind: 'screen-sharing-start'}) | |||
}, | |||
view() { | |||
const style = { | |||
overflow: 'scroll', | |||
backgroundColor: 'black', | |||
color: 'white', | |||
fontFamily: 'monospace', | |||
} | |||
return ScreenShare.isOn && m('div', {style}, | |||
m('video.screen[playsinline][autoplay]'), | |||
return ScreenShare.isOn && m('.screen-share', {style}, | |||
m('.streamer', `${ScreenShare.streamer}'s stream`), | |||
m('video.screen[playsinline][autoplay]', {srcObject: ScreenShare.stream}), | |||
) | |||
}, | |||
} | |||
addEventListener('screen-sharing-start', ({detail}) => { | |||
ScreenShare.streamer = detail.source | |||
}) | |||
addEventListener('screen-sharing-stop', ({detail}) => { | |||
if(ScreenShare.stream) { | |||
ScreenShare.stream.getTracks().forEach(track => track.stop()) | |||
ScreenShare.stream = null | |||
} | |||
ScreenShare.streamer = null | |||
}) | |||
addEventListener('load', () => { | |||
doNotLog.add('screen-share-start') | |||
doNotLog.add('screen-share-stop') | |||
Headers.push([ScreenShareConfig]) | |||
Apps.push([ScreenShare, {key: 'screen-share-container'}]) | |||
}) | |||
setTimeout(ScreenShare.requestScreen, 200) |
@@ -52,12 +52,6 @@ const updateOtherVideo = (target, dom) => { | |||
dom.srcObject = new MediaStream() | |||
let rpc = null | |||
const rpcConfig = {iceServers: [{ | |||
urls: ['stun:stun.pico.chat:5349', 'turn:turn.pico.chat:5349'], | |||
username: 'roderic', | |||
credential: 'tomodachi', | |||
}]} | |||
const stopRpc = () => { | |||
rpc && rpc.close() | |||
dom.srcObject.getTracks().forEach(track => { | |||
@@ -72,57 +66,7 @@ const updateOtherVideo = (target, dom) => { | |||
rpc = new RTCPeerConnection(rpcConfig) | |||
document.querySelector('video.self').srcObject.getTracks() | |||
.forEach(t => rpc.addTrack(t)) | |||
rpc.onicecandidate = ({candidate}) => { | |||
if(candidate && candidate.candidate) { | |||
const value = {type: 'candidate', candidate} | |||
wire({kind: 'peerInfo', value, target}) | |||
} | |||
} | |||
rpc.ontrack = ({track}) => { | |||
dom.srcObject.addTrack(track) | |||
dom.srcObject = dom.srcObject | |||
} | |||
rpc.onconnectionstatechange = () => { | |||
if(rpc.connectionState === 'failed') { | |||
console.log(target, 'failed, retry!') | |||
wire({kind: 'peerInfo', value: {type: 'request'}, target}) | |||
} | |||
} | |||
} | |||
dom.listener = async ({detail: {source, value}}) => { | |||
if(source !== target) { | |||
return | |||
} | |||
console.log(source, value.type) | |||
if(value.type === 'request') { | |||
resetRpc() | |||
const localOffer = await rpc.createOffer() | |||
await rpc.setLocalDescription(localOffer) | |||
wire({kind: 'peerInfo', value: localOffer, target}) | |||
} | |||
else if(value.type === 'offer') { | |||
resetRpc() | |||
const remoteOffer = new RTCSessionDescription(value) | |||
await rpc.setRemoteDescription(remoteOffer) | |||
const localAnswer = await rpc.createAnswer() | |||
await rpc.setLocalDescription(localAnswer) | |||
wire({kind: 'peerInfo', value: localAnswer, target}) | |||
} | |||
else if(value.type === 'answer') { | |||
const remoteAnswer = new RTCSessionDescription(value) | |||
await rpc.setRemoteDescription(remoteAnswer) | |||
} | |||
else if(value.type === 'candidate') { | |||
const candidate = new RTCIceCandidate(value.candidate) | |||
await rpc.addIceCandidate(candidate) | |||
} | |||
else if(value.type === 'stop') { | |||
stopRpc() | |||
} | |||
} | |||
addEventListener('peerInfo', dom.listener) | |||
} | |||
const Video = { | |||
setUp: (username) => ({dom}) => { |
@@ -6,11 +6,12 @@ | |||
<link rel="stylesheet" href="/apps/chat.css"/> | |||
<script src="/libs/mithril.min.js"></script> | |||
<script src="/libs/marked.min.js"></script> | |||
<script src="/libs/purify.min.js"></script> | |||
<!-- <script src="/libs/purify.min.js"></script> --> | |||
<script src="/pico.js" defer></script> | |||
<script src="/apps/streams.js"></script> | |||
<script src="/apps/screen.js"></script> | |||
<script src="/apps/chat.js"></script> | |||
<script src="/apps/rpc.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> |
@@ -37,7 +37,7 @@ listen('state', ({detail}) => { | |||
delete detail.kind | |||
Object.assign(State, detail) | |||
}) | |||
const doNotLog = new Set(['login', 'state', 'post', 'peerInfo', 'volumeMapMove']) | |||
const doNotLog = new Set(['state']) | |||
/* | |||
* | |||
* UTILS | |||
@@ -86,7 +86,6 @@ const connect = (username) => { | |||
} | |||
State.websocket.onclose = (e) => { | |||
State.online.forEach(signalPeerStop) | |||
if(!e.wasClean) { | |||
setTimeout(connect, 1000, username) | |||
} |