const Toggle = { view({attrs: {label, value}}) { const onclick = () => { StreamContainer[value] = !StreamContainer[value] requestStream() } const checked = StreamContainer[value] return m('label.styled', m('input[type=checkbox]', {checked, onclick}), label, ) } } const VideoConfig = { get video() { return StreamContainer.videoOn && State.online.length < 10 && params.get('v') !== '0' && {width: {ideal: 320}, facingMode: 'user', frameRate: 26} }, get audio() { return StreamContainer.audioOn && params.get('a') !== '0' }, view() { return [ m(Toggle, {label: 'video', value: 'videoOn'}), m(Toggle, {label: 'audio', value: 'audioOn'}), ] } } const requestStream = async () => { if(StreamContainer.videoOn || StreamContainer.audioOn) { await navigator.mediaDevices .getUserMedia(VideoConfig) .then(stream => signal({kind: 'stream', value: {source: State.username, stream}}) ) .catch(e => console.log(e)) } else { const stream = new MediaStream() signal({kind: 'stream', value: {source: State.username, stream}}) } } const Video = { setUp: (username) => ({dom}) => { if(username === State.username) { dom.muted = true requestStream() } }, view({attrs: {username}}) { const styleOuter = { position: 'relative', display: 'block', color: 'white', overflow: 'hidden', } const styleMeta = { position: 'absolute', display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%', fontFamily: 'monospace', fontSize: 'xxx-large', } const styleVideo = { objectFit: Settings.get('blackBars') ? 'contain' : 'cover', width: '100%', height: '100%', transform: username === State.username ? 'scaleX(-1)' : 'scaleX(1)', } return m('.video-container', {style: styleOuter}, m('.video-info', {style: styleMeta}, m('.username', username), ), m('video[playsinline][autoplay]', { style: styleVideo, srcObject: streams[username], oncreate: this.setUp(username), }), ) }, } const StreamContainer = { videoOn: true, audioOn: true, view() { const dims = [ Math.floor((1 + Math.sqrt(4 * State.online.length - 3)) / 2), Math.ceil(Math.sqrt(State.online.length)), ].map(n => Array(n || 1).fill('1fr').join(' ')) if(innerHeight > innerWidth) dims.reverse() const style = { backgroundColor: 'black', overflow: 'hidden', display: 'grid', gridTemplateRows: dims[0], gridTemplateColumns: dims[1], } return m('.videos', {style}, State.online.map(username => m(Video, {key: username, username})) ) }, } addEventListener('load', () => { Headers.push([VideoConfig]) Apps.push([StreamContainer, {key: 'stream-container'}]) })