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 = { update() { const stream = document.querySelector('video.self').srcObject stream.getTracks().forEach(track => { track.stop() stream.removeTrack(track) delete track }) if(VideoConfig.videoOn || VideoConfig.audioOn) { navigator.mediaDevices .getUserMedia(VideoConfig) .then(s => s.getTracks().forEach(t => stream.addTrack(t))) .catch(e => console.error(e)) } }, 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: ({dom}) => { dom.playsinline = true dom.autoplay = true dom.srcObject = new MediaStream() }, 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 StreamContainer = { 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})) ), ) }, }