|  |  |  |  |  |  | 
														
													
														
															|  |  | const scrollIntoView = (vnode) => { |  |  | const scrollIntoView = (vnode) => { | 
														
													
														
															|  |  | vnode.dom.scrollIntoView() |  |  | vnode.dom.scrollIntoView() | 
														
													
														
															|  |  | } |  |  | } | 
														
													
														
															|  |  |  |  |  | const toggleFullscreen = (el) => (event) => { | 
														
													
														
															|  |  |  |  |  | document.fullscreenElement ? document.exitFullscreen() : el.requestFullscreen() | 
														
													
														
															|  |  |  |  |  | } | 
														
													
														
															|  |  | const prettyTime = (ts) => { |  |  | const prettyTime = (ts) => { | 
														
													
														
															|  |  | const dt = new Date(ts) |  |  | const dt = new Date(ts) | 
														
													
														
															|  |  | const H = `0${dt.getHours()}`.slice(-2) |  |  | const H = `0${dt.getHours()}`.slice(-2) | 
														
													
												
													
														
															|  |  |  |  |  |  | 
														
													
														
															|  |  | dom.autoplay = true |  |  | dom.autoplay = true | 
														
													
														
															|  |  | dom.muted = (username === State.username) |  |  | dom.muted = (username === State.username) | 
														
													
														
															|  |  | dom.srcObject = stream |  |  | dom.srcObject = stream | 
														
													
														
															|  |  |  |  |  | dom.ondblclick = toggleFullscreen(dom) | 
														
													
														
															|  |  | }, |  |  | }, | 
														
													
														
															|  |  | view({attrs}) { |  |  | view({attrs}) { | 
														
													
														
															|  |  | const rpc = State.rpcs[attrs.username] || {iceConnectionState: m.trust(' ')} |  |  | const rpc = State.rpcs[attrs.username] || {iceConnectionState: m.trust(' ')} | 
														
													
														
															|  |  | return m('.video-container', |  |  | return m('.video-container', | 
														
													
														
															|  |  | m('.video-source', attrs.username), |  |  | m('.video-source', attrs.username), | 
														
													
														
															|  |  | m('.video-state', rpc.iceConnectionState), |  |  | m('.video-state', rpc.iceConnectionState), | 
														
													
														
															|  |  | m('video', {playsinline: true, oncreate: Video.appendStream(attrs)}), |  |  |  | 
														
													
														
															|  |  |  |  |  | m('video.mirrored', {playsinline: true, oncreate: Video.appendStream(attrs)}), | 
														
													
														
															|  |  | ) |  |  | ) | 
														
													
														
															|  |  | }, |  |  | }, | 
														
													
														
															|  |  | } |  |  | } | 
														
													
														
															|  |  |  |  |  | const audio = { | 
														
													
														
															|  |  |  |  |  | noiseSuppresion: true, | 
														
													
														
															|  |  |  |  |  | echoCancellation: true, | 
														
													
														
															|  |  |  |  |  | } | 
														
													
														
															|  |  | const Media = { |  |  | const Media = { | 
														
													
														
															|  |  | audioVideo: {audio: true, video: {width: {ideal: 320}, facingMode: 'user'}}, |  |  |  | 
														
													
														
															|  |  | audioOnly: {audio: true, video: false}, |  |  |  | 
														
													
														
															|  |  | turnOn: (constraints) => async () => { |  |  |  | 
														
													
														
															|  |  |  |  |  | options: { | 
														
													
														
															|  |  |  |  |  | video: {audio, video: {width: {ideal: 320}, facingMode: 'user'}}, | 
														
													
														
															|  |  |  |  |  | audio: {audio, video: false}, | 
														
													
														
															|  |  |  |  |  | screen: {audio, video: {mediaSource: 'screen'}}, | 
														
													
														
															|  |  |  |  |  | none: {audio: false, video: false}, | 
														
													
														
															|  |  |  |  |  | }, | 
														
													
														
															|  |  |  |  |  | turnOn: async () => { | 
														
													
														
															|  |  |  |  |  | const constraints = Media.options[document.querySelector('#media-source').value] | 
														
													
														
															|  |  | const media = await navigator.mediaDevices.getUserMedia(constraints) |  |  | const media = await navigator.mediaDevices.getUserMedia(constraints) | 
														
													
														
															|  |  | State.media[State.username] = media |  |  | State.media[State.username] = media | 
														
													
														
															|  |  | wire({kind: 'peerInfo', value: {type: 'request'}}) |  |  | wire({kind: 'peerInfo', value: {type: 'request'}}) | 
														
													
												
													
														
															|  |  |  |  |  |  | 
														
													
														
															|  |  | view() { |  |  | view() { | 
														
													
														
															|  |  | if(!State.media[State.username]) { |  |  | if(!State.media[State.username]) { | 
														
													
														
															|  |  | return m('.media', |  |  | return m('.media', | 
														
													
														
															|  |  | m('button', {onclick: Media.turnOn(Media.audioVideo)}, 'turn media on'), |  |  |  | 
														
													
														
															|  |  | m('button', {onclick: Media.turnOn(Media.audioOnly)}, 'turn audio on'), |  |  |  | 
														
													
														
															|  |  |  |  |  | m('button', {onclick: Media.turnOn}, 'turn on'), | 
														
													
														
															|  |  |  |  |  | m('select#media-source', | 
														
													
														
															|  |  |  |  |  | Object.keys(Media.options).map(description => m('option', description)), | 
														
													
														
															|  |  |  |  |  | ), | 
														
													
														
															|  |  | ) |  |  | ) | 
														
													
														
															|  |  | } |  |  | } | 
														
													
														
															|  |  | else { |  |  | else { | 
														
													
														
															|  |  | return m('.media', |  |  | return m('.media', | 
														
													
														
															|  |  | m('button', {onclick: Media.turnOff}, 'turn media off'), |  |  |  | 
														
													
														
															|  |  |  |  |  | m('button', {onclick: Media.turnOff}, 'turn off'), | 
														
													
														
															|  |  | m('.videos', |  |  | m('.videos', | 
														
													
														
															|  |  | Object.entries(State.media).map(([username, stream]) => |  |  | Object.entries(State.media).map(([username, stream]) => | 
														
													
														
															|  |  | m(Video, {username, stream}) |  |  | m(Video, {username, stream}) |