connectionsComponent = { html: `

Webcam and/or Audio
Video
Mic
Audio
Networking a la carte:
Webcam
Chat
World sync
`, init: (el) => new Proxy({ visible: true, webcam: [{profile:{name:"No thanks"},config: () => document.createElement('div')}], chatnetwork: [{profile:{name:"No thanks"},config: () => document.createElement('div')}], scene: [{profile:{name:"No thanks"},config: () => document.createElement('div')}], selectedWebcam: '', selectedChatnetwork:'', selectedScene: '', $webcam: $webcam = el.querySelector("#webcam"), $chatnetwork: $chatnetwork = el.querySelector("#chatnetwork"), $scene: $scene = el.querySelector("#scene"), $settings: $settings = el.querySelector("#settings"), $devices: $devices = el.querySelector("#devices"), $connect: $connect = el.querySelector("#connect"), $networking: $networking = el.querySelector("#networking"), $audioInput: el.querySelector('select#audioInput'), $audioOutput: el.querySelector('select#audioOutput'), $videoInput: el.querySelector('select#videoInput'), install(opts){ this.opts = opts; (['change']).map( (e) => el.addEventListener(e, (ev) => this[e] && this[e](ev.target.id,ev) ) ) this.reactToNetwork() $menu.buttons = ([ ` connect
` ]).concat($menu.buttons) if( document.location.href.match(/meet=/) ) this.show() setTimeout( () => document.dispatchEvent( new CustomEvent("$connections:ready", {detail: opts}) ), 1 ) }, toggle(){ $chat.visible = !$chat.visible }, change(id,e){ if( id.match(/^(webcam|chatnetwork|scene)$/) ){ this.renderSettings() // trigger this when 'change' event fires on children dom elements } }, show(opts){ opts = opts || {} if( opts.hide ){ if( el.parentElement ) el.parentElement.parentElement.style.display = 'none' // hide along with wrapper elements if( !opts.showChat ) $chat.visible = false }else{ $chat.visible = true this.visible = true // hide networking settings if entering thru meetinglink $networking.style.display = document.location.href.match(/meet=/) ? 'none' : 'block' if( !network.connected ){ document.querySelector('body > .xrf').appendChild(el) $chat.send({message:"", el, class:['ui']}) if( !network.meetinglink ){ // set default $webcam.value = opts.webcam || 'Peer2Peer' $chatnetwork.value = opts.chatnetwork || 'Peer2Peer' $scene.value = opts.scene || 'Peer2Peer' } this.renderSettings() }else{ $chat.send({message:"you are already connected, refresh page to create new connection",class:['info']}) } } }, update(){ this.selectedWebcam = $webcam.value this.selectedChatnetwork = $chatnetwork.value this.selectedScene = $scene.value }, forSelectedPluginsDo(cb){ // this function looks weird but it's handy to prevent the same plugins rendering duplicate configurations let plugins = {} let select = (name) => (o) => o.profile.name == name ? plugins[ o.profile.name ] = o : '' this.webcam.find( select(this.selectedWebcam) ) this.chatnetwork.find( select(this.selectedChatnetwork) ) this.scene.find( select(this.selectedScene) ) for( let i in plugins ){ try{ cb(plugins[i]) }catch(e){ console.error(e) } } }, renderSettings(){ let opts = {webcam: $webcam.value, chatnetwork: $chatnetwork.value, scene: $scene.value } this.update() $settings.innerHTML = '' this.forSelectedPluginsDo( (plugin) => $settings.appendChild( plugin.config({...opts,plugin}) ) ) this.renderInputs() }, renderInputs(){ if( !this.selectedWebcam || this.selectedWebcam == 'No thanks' ){ return this.$devices.style.display = 'none' }else this.$devices.style.display = '' navigator.mediaDevices.getUserMedia({ audio: true, video: true }) .then( () => { const selectors = [this.$audioInput, this.$audioOutput, this.$videoInput]; const gotDevices = (deviceInfos) => { // Handles being called several times to update labels. Preserve values. const values = selectors.map(select => select.value); selectors.forEach(select => { while (select.firstChild) { select.removeChild(select.firstChild); } }); for (let i = 0; i !== deviceInfos.length; ++i) { const deviceInfo = deviceInfos[i]; const option = document.createElement('option'); option.value = deviceInfo.deviceId; if (deviceInfo.kind === 'audioinput') { option.text = deviceInfo.label || `microphone ${this.$audioInput.length + 1}`; this.$audioInput.appendChild(option); } else if (deviceInfo.kind === 'audiooutput') { option.text = deviceInfo.label || `speaker ${this.$audioOutput.length + 1}`; this.$audioOutput.appendChild(option); } else if (deviceInfo.kind === 'videoinput') { option.text = deviceInfo.label || `camera this.${this.$videoInput.length + 1}`; this.$videoInput.appendChild(option); } else { console.log('Some other kind of source/device: ', deviceInfo); } } // hide if there's nothing to choose let totalDevices = this.$audioInput.options.length + this.$audioOutput.options.length + this.$videoInput.options.length this.$devices.style.display = totalDevices > 3 ? 'block' : 'none' selectors.forEach((select, selectorIndex) => { if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) { select.value = values[selectorIndex]; } }); } // after getUserMedia we can enumerate navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(console.warn); }) }, reactToNetwork(){ // *TODO* move to network? document.addEventListener('network.connect', () => { this.show({hide:true, showChat: true}) }) document.addEventListener('network.disconnect', () => { this.connected = false }) } },{ get(data,k,v){ return data[k] }, set(data,k,v){ data[k] = v switch( k ){ case "visible": el.style.display = v ? '' : 'none'; break; case "webcam": $webcam.innerHTML = ``; break; case "chatnetwork": $chatnetwork.innerHTML = ``; break; case "scene": $scene.innerHTML = ``; break; case "selectedScene": $scene.value = v; data.renderSettings(); break; case "selectedChatnetwork": $chatnetwork.value = v; data.renderSettings(); break; case "selectedWebcam": { $webcam.value = v; data.renderSettings(); $devices.style.display = v ? 'block' : 'none' break; } } } }) } // reactify component! document.addEventListener('$chat:ready', (opts) => { opts = opts.detail document.head.innerHTML += connectionsComponent.css window.$connections = document.createElement('div') $connections.innerHTML = connectionsComponent.html $connections = connectionsComponent.init($connections) $connections.install(opts) }) // alpine component for displaying meetings connectionsComponent.css = ` `