瀏覽代碼

Works?

master
Roderic Day 5 年之前
父節點
當前提交
a24942b7e1
共有 2 個檔案被更改,包括 108 行新增111 行删除
  1. +107
    -111
      apps/streams.js
  2. +1
    -0
      pico.js

+ 107
- 111
apps/streams.js 查看文件

}, },
toggle: (property) => () => { toggle: (property) => () => {
VideoConfig[property] = !VideoConfig[property] VideoConfig[property] = !VideoConfig[property]
VideoSelf.update()
updateSelfVideo()
} }
}) })
const VideoSelf = {
async update() {
const video = document.querySelector('video.self')
video.srcObject.getTracks().forEach(track => {
const updateSelfVideo = async () => {
const video = document.querySelector('video.self')

video.srcObject.getTracks().forEach(track => {
track.stop()
video.srcObject.removeTrack(track)
delete track
})

if(VideoConfig.videoOn || VideoConfig.audioOn) {
await navigator.mediaDevices
.getUserMedia(VideoConfig)
.then(s => s.getTracks().forEach(t => video.srcObject.addTrack(t)))
.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 rpcConfig = {iceServers: [{urls: 'stun:stun.sipgate.net:3478'}]}

const stopRpc = () => {
rpc && rpc.close()
dom.srcObject.getTracks().forEach(track => {
track.stop() track.stop()
video.srcObject.removeTrack(track)
dom.srcObject.removeTrack(track)
delete track delete track
}) })
stream = new MediaStream()
if(VideoConfig.videoOn || VideoConfig.audioOn) {
await navigator.mediaDevices
.getUserMedia(VideoConfig)
.then(s => s.getTracks().forEach(t => stream.addTrack(t)))
.catch(e => console.error(e))
}

const resetRpc = () => {
stopRpc()
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})
}
} }
video.srcObject = stream
wire({kind: 'peerInfo', value: {type: 'request'}})
},
setUp: ({dom}) => {
dom.playsinline = true
dom.autoplay = true
dom.muted = true
dom.srcObject = new MediaStream()
VideoSelf.update()
},
view({attrs: {username}}) {
const styleOuter = {
position: 'relative',
display: 'block',
backgroundColor: 'black',
color: 'white',
overflow: 'hidden',
rpc.ontrack = ({track}) => {
dom.srcObject.addTrack(track)
dom.srcObject = dom.srcObject
} }
const styleInner = {
objectFit: 'cover',
width: '100%',
height: '100%',
transform: 'scaleX(-1)',
}

dom.listener = async ({detail: {source, value}}) => {
if(source !== target) {
return
} }
return m('.video-container', {style: styleOuter},
m('.video-info', {style: {position: 'absolute', zIndex: 999}},
m('span', {style: {padding: '5px'}}, username),
),
m('video.self', {style: styleInner, oncreate: this.setUp}),
)
},
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 VideoOther = {
const Video = {
setUp: (username) => ({dom}) => { setUp: (username) => ({dom}) => {
dom.playsinline = true
dom.autoplay = true
dom.srcObject = new MediaStream() dom.srcObject = new MediaStream()
let rpc = null

const rpcConfig = {iceServers: [{urls: 'stun:stun.sipgate.net:3478'}]}

const stopRpc = () => {
rpc && rpc.close()
dom.srcObject.getTracks().forEach(track => {
track.stop()
dom.srcObject.removeTrack(track)
delete track
})
if(username === State.username) {
dom.classList.add('self')
dom.muted = true
updateSelfVideo()
} }

const resetRpc = () => {
stopRpc()
rpc = new RTCPeerConnection(rpcConfig)
dom.srcObject = new MediaStream()
document.querySelector('video.self').srcObject.getTracks()
.forEach(t => rpc.addTrack(t))

rpc.onicecandidate = ({candidate}) => {
if(candidate && candidate.candidate) {
wire({kind: 'peerInfo', value: {type: 'candidate', candidate}, target: username})
}
}
rpc.ontrack = ({track}) => {
dom.srcObject.addTrack(track)
}
}

const onPeerInfo = async ({detail: {source, value}}) => {
if(source !== username) {
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: username})
}
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: username})
}
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()
}
else {
updateOtherVideo(username, dom)
} }
},
tearDown: (username) => ({dom}) => {
removeEventListener('peerInfo', dom.listener)


addEventListener('peerInfo', onPeerInfo)
dom.srcObject.getTracks().forEach(track => {
track.stop()
dom.srcObject.removeTrack(track)
delete track
})
}, },
view({attrs: {username}}) { view({attrs: {username}}) {
const styleOuter = { const styleOuter = {
height: '100%', height: '100%',
transform: 'scaleX(-1)', transform: 'scaleX(-1)',
} }
return m('.video-container', {style: styleOuter},
return m('.video-container', {key: username, style: styleOuter},
m('.video-info', {style: {position: 'absolute', zIndex: 999}}, m('.video-info', {style: {position: 'absolute', zIndex: 999}},
m('span', {style: {padding: '5px'}}, username), m('span', {style: {padding: '5px'}}, username),
), ),
m('video', {style: styleInner, oncreate: this.setUp(username)}),
m('video[playsinline][autoplay]', {
style: styleInner,
oncreate: this.setUp(username),
onremove: this.tearDown(username),
}),
) )
}, },
} }
return '1fr' return '1fr'
}, },
view() { view() {
const dims = [StreamContainer.getRows(), StreamContainer.getColumns()]
if(screen.height > screen.width) dims.reverse()
const style = { const style = {
display: 'grid', display: 'grid',
padding: '3px', padding: '3px',
gridGap: '3px', gridGap: '3px',
height: '80vh',
gridTemplateColumns: StreamContainer.getColumns(),
gridTemplateRows: StreamContainer.getRows(),
height: '70vh',
gridTemplateRows: dims[0],
gridTemplateColumns: dims[1],
} }
return m('div', return m('div',
m('.video-controls', m('.video-controls',
m('button', {onclick: VideoConfig.toggle('audioOn')}, 'audio'), m('button', {onclick: VideoConfig.toggle('audioOn')}, 'audio'),
), ),
m('.videos', {style}, m('.videos', {style},
m(VideoSelf, {username: State.username}),
m(Video, {username: State.username}),
State.online.filter(username => username != State.username) State.online.filter(username => username != State.username)
.map(username => m(VideoOther, {username}))
.map(username => m(Video, {username}))
), ),
) )
}, },

+ 1
- 0
pico.js 查看文件

m('form.logout' + (State.isConnected ? '' : '.hidden'), m('form.logout' + (State.isConnected ? '' : '.hidden'),
{onsubmit: Base.sendLogout}, {onsubmit: Base.sendLogout},
m('button', 'Logout'), m('button', 'Logout'),
m('input[readonly]', {value: location}),
), ),
m('.error', State.info), m('.error', State.info),
), ),

Loading…
取消
儲存