|
- const VideoConfig = Object.seal({
- videoOn: true,
- audioOn: true,
- get video() {
- return VideoConfig.videoOn
- && {width: {ideal: 320}, facingMode: 'user', frameRate: 26}
- },
- get audio() {
- return VideoConfig.audioOn
- },
- toggle: (property) => () => {
- VideoConfig[property] = !VideoConfig[property]
- VideoSelf.update()
- }
- })
- const VideoSelf = {
- async update() {
- const video = document.querySelector('video.self')
- video.srcObject.getTracks().forEach(track => {
- track.stop()
- video.srcObject.removeTrack(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))
- }
- 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',
- }
- const styleInner = {
- objectFit: 'cover',
- width: '100%',
- height: '100%',
- transform: 'scaleX(-1)',
- }
- 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}),
- )
- },
- }
- const VideoOther = {
- setUp: (username) => ({dom}) => {
- dom.playsinline = true
- dom.autoplay = true
- 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
- })
- }
-
- 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()
- }
- }
-
- addEventListener('peerInfo', onPeerInfo)
- },
- view({attrs: {username}}) {
- const styleOuter = {
- position: 'relative',
- display: 'block',
- backgroundColor: 'black',
- color: 'white',
- overflow: 'hidden',
- }
- const styleInner = {
- objectFit: 'cover',
- width: '100%',
- height: '100%',
- transform: 'scaleX(-1)',
- }
- return m('.video-container', {style: styleOuter},
- m('.video-info', {style: {position: 'absolute', zIndex: 999}},
- m('span', {style: {padding: '5px'}}, username),
- ),
- m('video', {style: styleInner, oncreate: this.setUp(username)}),
- )
- },
- }
- const StreamContainer = {
- // screen.width, screen.height
- getColumns() {
- const n = State.online.length
- if(n > 4) return '1fr 1fr 1fr'
- if(n > 1) return '1fr 1fr'
- return '1fr'
- },
- getRows() {
- const n = State.online.length
- if(n > 6) return '1fr 1fr 1fr'
- if(n > 2) return '1fr 1fr'
- return '1fr'
- },
- view() {
- const style = {
- display: 'grid',
- padding: '3px',
- gridGap: '3px',
- height: '80vh',
- gridTemplateColumns: StreamContainer.getColumns(),
- gridTemplateRows: StreamContainer.getRows(),
- }
- return m('div',
- m('.video-controls',
- m('button', {onclick: VideoConfig.toggle('videoOn')}, 'video'),
- m('button', {onclick: VideoConfig.toggle('audioOn')}, 'audio'),
- ),
- m('.videos', {style},
- m(VideoSelf, {username: State.username}),
- State.online.filter(username => username != State.username)
- .map(username => m(VideoOther, {username}))
- ),
- )
- },
- }
-
- const signalPeerStop = (username) => signal({kind: 'peerInfo', value: {type: 'stop'}, source: username})
- addEventListener('pagehide', () => State.online.forEach(signalPeerStop))
- addEventListener('logout', () => State.online.forEach(signalPeerStop))
|