wip
This commit is contained in:
parent
5ec4c8ca8a
commit
edbc729a12
13 changed files with 1014 additions and 1276 deletions
1
dist/trystero-torrent.min.js
vendored
Normal file
1
dist/trystero-torrent.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
14
dist/utils.js
vendored
14
dist/utils.js
vendored
|
|
@ -55,13 +55,17 @@ function setupConsole(el){
|
|||
|
||||
function setupUrlBar(el,XRF){
|
||||
let inIframe = window.location !== window.parent.location
|
||||
let ids = ['#overlay','a#embed','a#source','a#model','#qrcode']
|
||||
let showButtons = () => {
|
||||
ids.map( (i) => $(i).style.display = 'block' )
|
||||
$('a#more').style.display = 'none'
|
||||
let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode']
|
||||
let showButtons = (state) => {
|
||||
ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' )
|
||||
$('a#more').style.display = state ? 'none' : 'inline-block'
|
||||
if( inIframe ) $('#uri').style.display = 'block'
|
||||
}
|
||||
$('a#more').addEventListener('click', () => showButtons() )
|
||||
$('a#more').addEventListener('click', () => showButtons(true) )
|
||||
$('a#meeting').addEventListener('click', () => {
|
||||
document.querySelector('a-scene').setAttribute('meeting', 'id: xrfragments')
|
||||
showButtons(false)
|
||||
})
|
||||
|
||||
XRF.addEventListener('hash', () => reflectUrl() )
|
||||
const reflectUrl = window.reflectUrl = (url) => {
|
||||
|
|
|
|||
290
dist/xrfragment.aframe.all.js
vendored
290
dist/xrfragment.aframe.all.js
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v0.5.1 generated at Tue Dec 12 06:06:16 PM CET 2023
|
||||
* v0.5.1 generated at Wed Dec 13 07:05:52 PM CET 2023
|
||||
* https://xrfragment.org
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
|
@ -2381,13 +2381,17 @@ function setupConsole(el){
|
|||
|
||||
function setupUrlBar(el,XRF){
|
||||
let inIframe = window.location !== window.parent.location
|
||||
let ids = ['#overlay','a#embed','a#source','a#model','#qrcode']
|
||||
let showButtons = () => {
|
||||
ids.map( (i) => $(i).style.display = 'block' )
|
||||
$('a#more').style.display = 'none'
|
||||
let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode']
|
||||
let showButtons = (state) => {
|
||||
ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' )
|
||||
$('a#more').style.display = state ? 'none' : 'inline-block'
|
||||
if( inIframe ) $('#uri').style.display = 'block'
|
||||
}
|
||||
$('a#more').addEventListener('click', () => showButtons() )
|
||||
$('a#more').addEventListener('click', () => showButtons(true) )
|
||||
$('a#meeting').addEventListener('click', () => {
|
||||
document.querySelector('a-scene').setAttribute('meeting', 'id: xrfragments')
|
||||
showButtons(false)
|
||||
})
|
||||
|
||||
XRF.addEventListener('hash', () => reflectUrl() )
|
||||
const reflectUrl = window.reflectUrl = (url) => {
|
||||
|
|
@ -2768,6 +2772,280 @@ window.AFRAME.registerComponent('xrf', {
|
|||
},
|
||||
|
||||
})
|
||||
AFRAME.registerComponent('meeting', {
|
||||
schema:{
|
||||
id:{ required:true, type:'string'}
|
||||
},
|
||||
init: function(){
|
||||
// embed https://github.com/dmotz/trystero (trystero-torrent.min.js build)
|
||||
|
||||
// add css+html
|
||||
let el = document.createElement("div")
|
||||
el.innerHTML += `<style type="text/css">
|
||||
#videos{
|
||||
display:grid-auto-columns;
|
||||
grid-column-gap:5px;
|
||||
margin-bottom:15px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 15px;
|
||||
z-index:1500;
|
||||
}
|
||||
#videos > video{
|
||||
border-radius:7px;
|
||||
display:inline-block;
|
||||
background:black;
|
||||
width:80px;
|
||||
height:60px;
|
||||
margin-right:5px;
|
||||
margin-bottom:5px;
|
||||
vertical-align:top;
|
||||
pointer-events:all;
|
||||
}
|
||||
#videos > video:hover{
|
||||
filter: brightness(1.8);
|
||||
cursor:pointer;
|
||||
}
|
||||
#chatbar {
|
||||
z-index: 1500;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
width: 48%;
|
||||
background: white;
|
||||
padding: 7px 0px 7px 15px;
|
||||
border-radius: 30px;
|
||||
max-width: 500px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 0px 5px 5px #0002;
|
||||
}
|
||||
#chatbar input{
|
||||
border:none;
|
||||
width:93%;
|
||||
}
|
||||
#chat{
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 110px;
|
||||
padding: 15px;
|
||||
pointer-events: none;
|
||||
}
|
||||
#chat .msg{
|
||||
background: #fffc;
|
||||
display: inline-block;
|
||||
padding: 6px 17px;
|
||||
border-radius: 20px;
|
||||
color: #000c;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#chat,#chatbar,#chatbar *, #chat *{
|
||||
font-family:monospace;
|
||||
font-size:16px;
|
||||
}
|
||||
#chatbar * {
|
||||
font-size:20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="videos" style="pointer-events:none"></div>
|
||||
<div id="chat"></div>
|
||||
<div id="chatbar">
|
||||
<input id="chatline" type="text" placeholder="chat here"></input>
|
||||
</div>`
|
||||
document.body.appendChild(el)
|
||||
this.trysteroInit()
|
||||
},
|
||||
|
||||
trysteroInit: async function(){
|
||||
const { joinRoom } = await import("/dist/trystero-torrent.min.js");
|
||||
|
||||
const roomname = document.location.href.replace(/#.*/,'')
|
||||
const config = this.config = {appId: this.data.id }
|
||||
const room = this.room = joinRoom(config, roomname )
|
||||
console.log("starting webrtc room: "+roomname)
|
||||
|
||||
const idsToNames = this.idsToNames = {}
|
||||
const [sendName, getName] = room.makeAction('name')
|
||||
const [sendChat, getChat] = this.room.makeAction('chat')
|
||||
this.sendChat = sendChat
|
||||
this.sendName = sendName
|
||||
this.getChat = getChat
|
||||
this.getName = getName
|
||||
|
||||
// tell other peers currently in the room our name
|
||||
let name = prompt('enter your name:')
|
||||
idsToNames[ room.selfId ] = name.substr(0,15)
|
||||
sendName( name )
|
||||
|
||||
// listen for peers naming themselves
|
||||
getName((name, peerId) => (idsToNames[peerId] = name))
|
||||
|
||||
this.initChat()
|
||||
|
||||
// this object can store audio instances for later
|
||||
const peerAudios = this.peerAudios = {}
|
||||
const peerVideos = this.peerVideos = {}
|
||||
// get a local audio stream from the microphone
|
||||
const selfStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
video: {
|
||||
width: 320,
|
||||
height: 240,
|
||||
frameRate: {
|
||||
ideal: 30,
|
||||
min: 10
|
||||
}
|
||||
}
|
||||
})
|
||||
let meVideo = this.addVideo(selfStream, room.selfId)
|
||||
meVideo.muted = true
|
||||
|
||||
// send stream to peers currently in the room
|
||||
room.addStream(selfStream)
|
||||
|
||||
// send stream + chatlog to peers who join later
|
||||
room.onPeerJoin( (peerId) => {
|
||||
console.log(`${idsToNames[peerId] || 'a visitor'} joined`)
|
||||
room.addStream(selfStream, peerId)
|
||||
sendName( name, peerId)
|
||||
this.sendChat({prime: this.chat.log}, peerId )
|
||||
})
|
||||
|
||||
room.onPeerLeave( (peerId) => {
|
||||
console.log(`${idsToNames[peerId] || 'a visitor'} left`)
|
||||
if( peerVideos[peerId] ){
|
||||
peerVideos[peerId].remove()
|
||||
delete peerVideos[peerId]
|
||||
}
|
||||
delete idsToNames[peerId]
|
||||
})
|
||||
|
||||
// handle streams from other peers
|
||||
room.onPeerStream((stream, peerId) => {
|
||||
// create an audio instance and set the incoming stream
|
||||
const audio = new Audio()
|
||||
audio.srcObject = stream
|
||||
audio.autoplay = true
|
||||
|
||||
// add the audio to peerAudio object if you want to address it for something
|
||||
// later (volume, etc.)
|
||||
peerAudios[peerId] = audio
|
||||
})
|
||||
|
||||
room.onPeerStream((stream, peerId) => {
|
||||
this.addVideo(stream,peerId)
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
addVideo: function(stream,peerId){
|
||||
let video = this.peerVideos[peerId]
|
||||
const videoContainer = document.getElementById('videos')
|
||||
// if this peer hasn't sent a stream before, create a video element
|
||||
if (!video) {
|
||||
video = document.createElement('video')
|
||||
video.autoplay = true
|
||||
|
||||
// add video element to the DOM
|
||||
videoContainer.appendChild(video)
|
||||
}
|
||||
|
||||
video.srcObject = stream
|
||||
video.resize = (state) => {
|
||||
if( video.resize.state == undefined ) video.resize.state = false
|
||||
if( state == undefined ) state = (video.resize.state = !video.resize.state )
|
||||
video.style.width = state ? '320px' : '80px'
|
||||
video.style.height = state ? '200px' : '60px'
|
||||
}
|
||||
|
||||
video.addEventListener('click', () => video.resize() )
|
||||
|
||||
this.peerVideos[peerId] = video
|
||||
return video
|
||||
},
|
||||
|
||||
createElement: function(str){
|
||||
let el = document.createElement("div")
|
||||
el.className = "msg"
|
||||
el.innerHTML = this.linkify(str)
|
||||
return el
|
||||
},
|
||||
|
||||
// central function to broadcast stuff to chat
|
||||
send: function(str){
|
||||
if( !this.sendChat ) return
|
||||
this.sendChat({content:str}) // send to network
|
||||
this.chat.append(str) // send to screen
|
||||
},
|
||||
|
||||
initChat: function(){
|
||||
|
||||
let chatline = this.chatline = document.querySelector("#chatline")
|
||||
let chat = this.chat = document.querySelector("#chat")
|
||||
chat.log = [] // save raw chatlog to prime new visitors
|
||||
chat.append = (str) => {
|
||||
if( !str ) return
|
||||
str = str.replace('\n', "<br>")
|
||||
this.chat.appendChild(this.createElement(str)) // send to screen
|
||||
this.chat.innerHTML += '<br>'
|
||||
this.chat.log.push(str)
|
||||
}
|
||||
|
||||
let send = () => {
|
||||
let str = `${this.idsToNames[ this.room.selfId ]}: ${chatline.value.substr(0,65515).trim()}`
|
||||
this.send(str)
|
||||
}
|
||||
|
||||
chatline.addEventListener("keydown", (e) => {
|
||||
if( e.key !== "Enter" ) return
|
||||
send()
|
||||
chatline.value = ''
|
||||
})
|
||||
|
||||
// listen for chatmsg
|
||||
this.getChat((data, peerId) => {
|
||||
if( data.prime ){
|
||||
if( this.chat.primed ) return // only prime once
|
||||
console.log("receiving prime")
|
||||
data.prime.map( (l) => chat.append(l) ) // send log to screen
|
||||
this.chat.primed = true
|
||||
}
|
||||
chat.append(data.content) // send to screen
|
||||
})
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
linkify: function(t){
|
||||
const isValidHttpUrl = s => {
|
||||
let u
|
||||
try {u = new URL(s)}
|
||||
catch (_) {return false}
|
||||
return u.protocol.startsWith("http")
|
||||
}
|
||||
const m = t.match(/(?<=\s|^)[a-zA-Z0-9-:/]+\.[a-zA-Z0-9-].+?(?=[.,;:?!-]?(?:\s|$))/g)
|
||||
if (!m) return t
|
||||
const a = []
|
||||
m.forEach(x => {
|
||||
const [t1, ...t2] = t.split(x)
|
||||
a.push(t1)
|
||||
t = t2.join(x)
|
||||
const y = (!(x.match(/:\/\//)) ? 'https://' : '') + x
|
||||
if (isNaN(x) && isValidHttpUrl(y))
|
||||
a.push('<a href="' + y + '" target="_blank" style="pointer-events:all">' + y.split('/')[2] + '</a>')
|
||||
else
|
||||
a.push(x)
|
||||
})
|
||||
a.push(t)
|
||||
return a.join('')
|
||||
}
|
||||
|
||||
});
|
||||
// look-controls turns off autoUpdateMatrix (of player) which
|
||||
// will break teleporting and other stuff
|
||||
// overriding this is easier then adding updateMatrixWorld() everywhere else
|
||||
|
|
|
|||
290
dist/xrfragment.aframe.js
vendored
290
dist/xrfragment.aframe.js
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v0.5.1 generated at Tue Dec 12 06:06:16 PM CET 2023
|
||||
* v0.5.1 generated at Wed Dec 13 07:05:52 PM CET 2023
|
||||
* https://xrfragment.org
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
|
@ -2375,13 +2375,17 @@ function setupConsole(el){
|
|||
|
||||
function setupUrlBar(el,XRF){
|
||||
let inIframe = window.location !== window.parent.location
|
||||
let ids = ['#overlay','a#embed','a#source','a#model','#qrcode']
|
||||
let showButtons = () => {
|
||||
ids.map( (i) => $(i).style.display = 'block' )
|
||||
$('a#more').style.display = 'none'
|
||||
let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode']
|
||||
let showButtons = (state) => {
|
||||
ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' )
|
||||
$('a#more').style.display = state ? 'none' : 'inline-block'
|
||||
if( inIframe ) $('#uri').style.display = 'block'
|
||||
}
|
||||
$('a#more').addEventListener('click', () => showButtons() )
|
||||
$('a#more').addEventListener('click', () => showButtons(true) )
|
||||
$('a#meeting').addEventListener('click', () => {
|
||||
document.querySelector('a-scene').setAttribute('meeting', 'id: xrfragments')
|
||||
showButtons(false)
|
||||
})
|
||||
|
||||
XRF.addEventListener('hash', () => reflectUrl() )
|
||||
const reflectUrl = window.reflectUrl = (url) => {
|
||||
|
|
@ -2762,6 +2766,280 @@ window.AFRAME.registerComponent('xrf', {
|
|||
},
|
||||
|
||||
})
|
||||
AFRAME.registerComponent('meeting', {
|
||||
schema:{
|
||||
id:{ required:true, type:'string'}
|
||||
},
|
||||
init: function(){
|
||||
// embed https://github.com/dmotz/trystero (trystero-torrent.min.js build)
|
||||
|
||||
// add css+html
|
||||
let el = document.createElement("div")
|
||||
el.innerHTML += `<style type="text/css">
|
||||
#videos{
|
||||
display:grid-auto-columns;
|
||||
grid-column-gap:5px;
|
||||
margin-bottom:15px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 15px;
|
||||
z-index:1500;
|
||||
}
|
||||
#videos > video{
|
||||
border-radius:7px;
|
||||
display:inline-block;
|
||||
background:black;
|
||||
width:80px;
|
||||
height:60px;
|
||||
margin-right:5px;
|
||||
margin-bottom:5px;
|
||||
vertical-align:top;
|
||||
pointer-events:all;
|
||||
}
|
||||
#videos > video:hover{
|
||||
filter: brightness(1.8);
|
||||
cursor:pointer;
|
||||
}
|
||||
#chatbar {
|
||||
z-index: 1500;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
width: 48%;
|
||||
background: white;
|
||||
padding: 7px 0px 7px 15px;
|
||||
border-radius: 30px;
|
||||
max-width: 500px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 0px 5px 5px #0002;
|
||||
}
|
||||
#chatbar input{
|
||||
border:none;
|
||||
width:93%;
|
||||
}
|
||||
#chat{
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 110px;
|
||||
padding: 15px;
|
||||
pointer-events: none;
|
||||
}
|
||||
#chat .msg{
|
||||
background: #fffc;
|
||||
display: inline-block;
|
||||
padding: 6px 17px;
|
||||
border-radius: 20px;
|
||||
color: #000c;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#chat,#chatbar,#chatbar *, #chat *{
|
||||
font-family:monospace;
|
||||
font-size:16px;
|
||||
}
|
||||
#chatbar * {
|
||||
font-size:20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="videos" style="pointer-events:none"></div>
|
||||
<div id="chat"></div>
|
||||
<div id="chatbar">
|
||||
<input id="chatline" type="text" placeholder="chat here"></input>
|
||||
</div>`
|
||||
document.body.appendChild(el)
|
||||
this.trysteroInit()
|
||||
},
|
||||
|
||||
trysteroInit: async function(){
|
||||
const { joinRoom } = await import("/dist/trystero-torrent.min.js");
|
||||
|
||||
const roomname = document.location.href.replace(/#.*/,'')
|
||||
const config = this.config = {appId: this.data.id }
|
||||
const room = this.room = joinRoom(config, roomname )
|
||||
console.log("starting webrtc room: "+roomname)
|
||||
|
||||
const idsToNames = this.idsToNames = {}
|
||||
const [sendName, getName] = room.makeAction('name')
|
||||
const [sendChat, getChat] = this.room.makeAction('chat')
|
||||
this.sendChat = sendChat
|
||||
this.sendName = sendName
|
||||
this.getChat = getChat
|
||||
this.getName = getName
|
||||
|
||||
// tell other peers currently in the room our name
|
||||
let name = prompt('enter your name:')
|
||||
idsToNames[ room.selfId ] = name.substr(0,15)
|
||||
sendName( name )
|
||||
|
||||
// listen for peers naming themselves
|
||||
getName((name, peerId) => (idsToNames[peerId] = name))
|
||||
|
||||
this.initChat()
|
||||
|
||||
// this object can store audio instances for later
|
||||
const peerAudios = this.peerAudios = {}
|
||||
const peerVideos = this.peerVideos = {}
|
||||
// get a local audio stream from the microphone
|
||||
const selfStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
video: {
|
||||
width: 320,
|
||||
height: 240,
|
||||
frameRate: {
|
||||
ideal: 30,
|
||||
min: 10
|
||||
}
|
||||
}
|
||||
})
|
||||
let meVideo = this.addVideo(selfStream, room.selfId)
|
||||
meVideo.muted = true
|
||||
|
||||
// send stream to peers currently in the room
|
||||
room.addStream(selfStream)
|
||||
|
||||
// send stream + chatlog to peers who join later
|
||||
room.onPeerJoin( (peerId) => {
|
||||
console.log(`${idsToNames[peerId] || 'a visitor'} joined`)
|
||||
room.addStream(selfStream, peerId)
|
||||
sendName( name, peerId)
|
||||
this.sendChat({prime: this.chat.log}, peerId )
|
||||
})
|
||||
|
||||
room.onPeerLeave( (peerId) => {
|
||||
console.log(`${idsToNames[peerId] || 'a visitor'} left`)
|
||||
if( peerVideos[peerId] ){
|
||||
peerVideos[peerId].remove()
|
||||
delete peerVideos[peerId]
|
||||
}
|
||||
delete idsToNames[peerId]
|
||||
})
|
||||
|
||||
// handle streams from other peers
|
||||
room.onPeerStream((stream, peerId) => {
|
||||
// create an audio instance and set the incoming stream
|
||||
const audio = new Audio()
|
||||
audio.srcObject = stream
|
||||
audio.autoplay = true
|
||||
|
||||
// add the audio to peerAudio object if you want to address it for something
|
||||
// later (volume, etc.)
|
||||
peerAudios[peerId] = audio
|
||||
})
|
||||
|
||||
room.onPeerStream((stream, peerId) => {
|
||||
this.addVideo(stream,peerId)
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
addVideo: function(stream,peerId){
|
||||
let video = this.peerVideos[peerId]
|
||||
const videoContainer = document.getElementById('videos')
|
||||
// if this peer hasn't sent a stream before, create a video element
|
||||
if (!video) {
|
||||
video = document.createElement('video')
|
||||
video.autoplay = true
|
||||
|
||||
// add video element to the DOM
|
||||
videoContainer.appendChild(video)
|
||||
}
|
||||
|
||||
video.srcObject = stream
|
||||
video.resize = (state) => {
|
||||
if( video.resize.state == undefined ) video.resize.state = false
|
||||
if( state == undefined ) state = (video.resize.state = !video.resize.state )
|
||||
video.style.width = state ? '320px' : '80px'
|
||||
video.style.height = state ? '200px' : '60px'
|
||||
}
|
||||
|
||||
video.addEventListener('click', () => video.resize() )
|
||||
|
||||
this.peerVideos[peerId] = video
|
||||
return video
|
||||
},
|
||||
|
||||
createElement: function(str){
|
||||
let el = document.createElement("div")
|
||||
el.className = "msg"
|
||||
el.innerHTML = this.linkify(str)
|
||||
return el
|
||||
},
|
||||
|
||||
// central function to broadcast stuff to chat
|
||||
send: function(str){
|
||||
if( !this.sendChat ) return
|
||||
this.sendChat({content:str}) // send to network
|
||||
this.chat.append(str) // send to screen
|
||||
},
|
||||
|
||||
initChat: function(){
|
||||
|
||||
let chatline = this.chatline = document.querySelector("#chatline")
|
||||
let chat = this.chat = document.querySelector("#chat")
|
||||
chat.log = [] // save raw chatlog to prime new visitors
|
||||
chat.append = (str) => {
|
||||
if( !str ) return
|
||||
str = str.replace('\n', "<br>")
|
||||
this.chat.appendChild(this.createElement(str)) // send to screen
|
||||
this.chat.innerHTML += '<br>'
|
||||
this.chat.log.push(str)
|
||||
}
|
||||
|
||||
let send = () => {
|
||||
let str = `${this.idsToNames[ this.room.selfId ]}: ${chatline.value.substr(0,65515).trim()}`
|
||||
this.send(str)
|
||||
}
|
||||
|
||||
chatline.addEventListener("keydown", (e) => {
|
||||
if( e.key !== "Enter" ) return
|
||||
send()
|
||||
chatline.value = ''
|
||||
})
|
||||
|
||||
// listen for chatmsg
|
||||
this.getChat((data, peerId) => {
|
||||
if( data.prime ){
|
||||
if( this.chat.primed ) return // only prime once
|
||||
console.log("receiving prime")
|
||||
data.prime.map( (l) => chat.append(l) ) // send log to screen
|
||||
this.chat.primed = true
|
||||
}
|
||||
chat.append(data.content) // send to screen
|
||||
})
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
linkify: function(t){
|
||||
const isValidHttpUrl = s => {
|
||||
let u
|
||||
try {u = new URL(s)}
|
||||
catch (_) {return false}
|
||||
return u.protocol.startsWith("http")
|
||||
}
|
||||
const m = t.match(/(?<=\s|^)[a-zA-Z0-9-:/]+\.[a-zA-Z0-9-].+?(?=[.,;:?!-]?(?:\s|$))/g)
|
||||
if (!m) return t
|
||||
const a = []
|
||||
m.forEach(x => {
|
||||
const [t1, ...t2] = t.split(x)
|
||||
a.push(t1)
|
||||
t = t2.join(x)
|
||||
const y = (!(x.match(/:\/\//)) ? 'https://' : '') + x
|
||||
if (isNaN(x) && isValidHttpUrl(y))
|
||||
a.push('<a href="' + y + '" target="_blank" style="pointer-events:all">' + y.split('/')[2] + '</a>')
|
||||
else
|
||||
a.push(x)
|
||||
})
|
||||
a.push(t)
|
||||
return a.join('')
|
||||
}
|
||||
|
||||
});
|
||||
// look-controls turns off autoUpdateMatrix (of player) which
|
||||
// will break teleporting and other stuff
|
||||
// overriding this is easier then adding updateMatrixWorld() everywhere else
|
||||
|
|
|
|||
1360
dist/xrfragment.module.js
vendored
1360
dist/xrfragment.module.js
vendored
File diff suppressed because it is too large
Load diff
2
dist/xrfragment.three.js
vendored
2
dist/xrfragment.three.js
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v0.5.1 generated at Tue Dec 12 06:06:16 PM CET 2023
|
||||
* v0.5.1 generated at Wed Dec 13 07:05:52 PM CET 2023
|
||||
* https://xrfragment.org
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
|
|
|||
2
dist/xrfragment.three.module.js
vendored
2
dist/xrfragment.three.module.js
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v0.5.1 generated at Tue Dec 12 06:06:16 PM CET 2023
|
||||
* v0.5.1 generated at Wed Dec 13 07:05:52 PM CET 2023
|
||||
* https://xrfragment.org
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -20,13 +20,7 @@
|
|||
<input type="text" id="uri" value="" onchange="AFRAME.XRF.navigator.to( $('#uri').value )" style="display:none"/>
|
||||
</div>
|
||||
|
||||
<!-- open AFRAME inspector: $('a-scene').components.inspector.openInspector() -->
|
||||
<a class="btn-foot" id="source" target="_blank" href="https://github.com/coderofsalvation/xrfragment-helloworld">➕ host</a>
|
||||
<a class="btn-foot" id="embed" target="_blank" onclick="window.embed()">🔗 share</a>
|
||||
<a class="btn-foot" id="model" target="_blank" onclick="window.download()">⬇️ scene</a>
|
||||
<a class="btn-foot" id="more" target="_blank">XRF</a>
|
||||
<textarea style="display:none"></textarea>
|
||||
<canvas id="qrcode" style="display:none" width="300" height="300"></canvas>
|
||||
|
||||
<a-scene xr-mode-ui="XRMode: xr" renderer="colorManagement: true; highRefreshRate:true" light="defaultLightsEnabled: false">
|
||||
<a-entity id="player" wasd-controls look-controls>
|
||||
|
|
@ -96,5 +90,15 @@
|
|||
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- open AFRAME inspector: $('a-scene').components.inspector.openInspector() -->
|
||||
<div class="footer">
|
||||
<canvas id="qrcode" class="btn-foot" style="display:none" width="121" height="121"></canvas>
|
||||
<a class="btn-foot" id="meeting" target="_blank">🧑🤝🧑 meeting</a>
|
||||
<a class="btn-foot" id="clone" target="_blank" href="https://github.com/coderofsalvation/xrfragment-helloworld">➕ clone</a>
|
||||
<a class="btn-foot" id="embed" target="_blank" onclick="window.embed()">🔗 share</a>
|
||||
<a class="btn-foot" id="model" target="_blank" onclick="window.download()">⬇️ scene</a>
|
||||
<a class="btn-foot" id="more" target="_blank">⚡</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
BIN
example/assets/blocky.glb
Normal file
BIN
example/assets/blocky.glb
Normal file
Binary file not shown.
|
|
@ -120,7 +120,7 @@ input[type="submit"] {
|
|||
text-decoration:none;
|
||||
}
|
||||
|
||||
a.btn-foot#embed{
|
||||
a.btn-foot#clone{
|
||||
color: #888;
|
||||
bottom: 129px;
|
||||
}
|
||||
|
|
@ -133,14 +133,20 @@ a.btn-foot#more{
|
|||
bottom:72px;
|
||||
width: 37px;
|
||||
min-width: 37px;
|
||||
font-size:23px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a.btn-foot#source{
|
||||
a.btn-foot#embed{
|
||||
bottom:186px;
|
||||
}
|
||||
|
||||
a#source,
|
||||
a.btn-foot#meeting{
|
||||
bottom:246px;
|
||||
}
|
||||
|
||||
a#meeting,
|
||||
a#clone,
|
||||
a#embed,
|
||||
a#model{
|
||||
display:none
|
||||
|
|
@ -217,9 +223,9 @@ html{
|
|||
transition: all ease .5s;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 4px 0 #0007;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.js-snackbar {
|
||||
|
|
@ -291,18 +297,15 @@ html{
|
|||
|
||||
#qrcode{
|
||||
z-index: 2000;
|
||||
position: absolute;
|
||||
border: 5px solid #1c1c3299;
|
||||
top: 66px;
|
||||
right: 20px;
|
||||
width: 175px;
|
||||
width: 111px;
|
||||
border-radius: 20px;
|
||||
background: #fff;
|
||||
border: 5px solid #888;
|
||||
border-radius: 15px;
|
||||
margin: 20px 20px;
|
||||
overflow: hidden;
|
||||
-webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC);
|
||||
bottom: 306px;
|
||||
height: 121px;
|
||||
right: 19px;
|
||||
}
|
||||
|
||||
input#share{
|
||||
|
|
|
|||
|
|
@ -55,13 +55,17 @@ export function setupConsole(el){
|
|||
|
||||
export function setupUrlBar(el,XRF){
|
||||
let inIframe = window.location !== window.parent.location
|
||||
let ids = ['#overlay','a#embed','a#source','a#model','#qrcode']
|
||||
let showButtons = () => {
|
||||
ids.map( (i) => $(i).style.display = 'block' )
|
||||
$('a#more').style.display = 'none'
|
||||
let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode']
|
||||
let showButtons = (state) => {
|
||||
ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' )
|
||||
$('a#more').style.display = state ? 'none' : 'inline-block'
|
||||
if( inIframe ) $('#uri').style.display = 'block'
|
||||
}
|
||||
$('a#more').addEventListener('click', () => showButtons() )
|
||||
$('a#more').addEventListener('click', () => showButtons(true) )
|
||||
$('a#meeting').addEventListener('click', () => {
|
||||
document.querySelector('a-scene').setAttribute('meeting', 'id: xrfragments')
|
||||
showButtons(false)
|
||||
})
|
||||
|
||||
XRF.addEventListener('hash', () => reflectUrl() )
|
||||
const reflectUrl = window.reflectUrl = (url) => {
|
||||
|
|
|
|||
BIN
example/assets/sloth.glb
Normal file
BIN
example/assets/sloth.glb
Normal file
Binary file not shown.
274
src/3rd/js/aframe/meeting.js
Normal file
274
src/3rd/js/aframe/meeting.js
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
AFRAME.registerComponent('meeting', {
|
||||
schema:{
|
||||
id:{ required:true, type:'string'}
|
||||
},
|
||||
init: function(){
|
||||
// embed https://github.com/dmotz/trystero (trystero-torrent.min.js build)
|
||||
|
||||
// add css+html
|
||||
let el = document.createElement("div")
|
||||
el.innerHTML += `<style type="text/css">
|
||||
#videos{
|
||||
display:grid-auto-columns;
|
||||
grid-column-gap:5px;
|
||||
margin-bottom:15px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 15px;
|
||||
z-index:1500;
|
||||
}
|
||||
#videos > video{
|
||||
border-radius:7px;
|
||||
display:inline-block;
|
||||
background:black;
|
||||
width:80px;
|
||||
height:60px;
|
||||
margin-right:5px;
|
||||
margin-bottom:5px;
|
||||
vertical-align:top;
|
||||
pointer-events:all;
|
||||
}
|
||||
#videos > video:hover{
|
||||
filter: brightness(1.8);
|
||||
cursor:pointer;
|
||||
}
|
||||
#chatbar {
|
||||
z-index: 1500;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
width: 48%;
|
||||
background: white;
|
||||
padding: 7px 0px 7px 15px;
|
||||
border-radius: 30px;
|
||||
max-width: 500px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 0px 5px 5px #0002;
|
||||
}
|
||||
#chatbar input{
|
||||
border:none;
|
||||
width:93%;
|
||||
}
|
||||
#chat{
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 110px;
|
||||
padding: 15px;
|
||||
pointer-events: none;
|
||||
}
|
||||
#chat .msg{
|
||||
background: #fffc;
|
||||
display: inline-block;
|
||||
padding: 6px 17px;
|
||||
border-radius: 20px;
|
||||
color: #000c;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#chat,#chatbar,#chatbar *, #chat *{
|
||||
font-family:monospace;
|
||||
font-size:16px;
|
||||
}
|
||||
#chatbar * {
|
||||
font-size:20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="videos" style="pointer-events:none"></div>
|
||||
<div id="chat"></div>
|
||||
<div id="chatbar">
|
||||
<input id="chatline" type="text" placeholder="chat here"></input>
|
||||
</div>`
|
||||
document.body.appendChild(el)
|
||||
this.trysteroInit()
|
||||
},
|
||||
|
||||
trysteroInit: async function(){
|
||||
const { joinRoom } = await import("/dist/trystero-torrent.min.js");
|
||||
|
||||
const roomname = document.location.href.replace(/#.*/,'')
|
||||
const config = this.config = {appId: this.data.id }
|
||||
const room = this.room = joinRoom(config, roomname )
|
||||
console.log("starting webrtc room: "+roomname)
|
||||
|
||||
const idsToNames = this.idsToNames = {}
|
||||
const [sendName, getName] = room.makeAction('name')
|
||||
const [sendChat, getChat] = this.room.makeAction('chat')
|
||||
this.sendChat = sendChat
|
||||
this.sendName = sendName
|
||||
this.getChat = getChat
|
||||
this.getName = getName
|
||||
|
||||
// tell other peers currently in the room our name
|
||||
let name = prompt('enter your name:')
|
||||
idsToNames[ room.selfId ] = name.substr(0,15)
|
||||
sendName( name )
|
||||
|
||||
// listen for peers naming themselves
|
||||
getName((name, peerId) => (idsToNames[peerId] = name))
|
||||
|
||||
this.initChat()
|
||||
|
||||
// this object can store audio instances for later
|
||||
const peerAudios = this.peerAudios = {}
|
||||
const peerVideos = this.peerVideos = {}
|
||||
// get a local audio stream from the microphone
|
||||
const selfStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
video: {
|
||||
width: 320,
|
||||
height: 240,
|
||||
frameRate: {
|
||||
ideal: 30,
|
||||
min: 10
|
||||
}
|
||||
}
|
||||
})
|
||||
let meVideo = this.addVideo(selfStream, room.selfId)
|
||||
meVideo.muted = true
|
||||
|
||||
// send stream to peers currently in the room
|
||||
room.addStream(selfStream)
|
||||
|
||||
// send stream + chatlog to peers who join later
|
||||
room.onPeerJoin( (peerId) => {
|
||||
console.log(`${idsToNames[peerId] || 'a visitor'} joined`)
|
||||
room.addStream(selfStream, peerId)
|
||||
sendName( name, peerId)
|
||||
this.sendChat({prime: this.chat.log}, peerId )
|
||||
})
|
||||
|
||||
room.onPeerLeave( (peerId) => {
|
||||
console.log(`${idsToNames[peerId] || 'a visitor'} left`)
|
||||
if( peerVideos[peerId] ){
|
||||
peerVideos[peerId].remove()
|
||||
delete peerVideos[peerId]
|
||||
}
|
||||
delete idsToNames[peerId]
|
||||
})
|
||||
|
||||
// handle streams from other peers
|
||||
room.onPeerStream((stream, peerId) => {
|
||||
// create an audio instance and set the incoming stream
|
||||
const audio = new Audio()
|
||||
audio.srcObject = stream
|
||||
audio.autoplay = true
|
||||
|
||||
// add the audio to peerAudio object if you want to address it for something
|
||||
// later (volume, etc.)
|
||||
peerAudios[peerId] = audio
|
||||
})
|
||||
|
||||
room.onPeerStream((stream, peerId) => {
|
||||
this.addVideo(stream,peerId)
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
addVideo: function(stream,peerId){
|
||||
let video = this.peerVideos[peerId]
|
||||
const videoContainer = document.getElementById('videos')
|
||||
// if this peer hasn't sent a stream before, create a video element
|
||||
if (!video) {
|
||||
video = document.createElement('video')
|
||||
video.autoplay = true
|
||||
|
||||
// add video element to the DOM
|
||||
videoContainer.appendChild(video)
|
||||
}
|
||||
|
||||
video.srcObject = stream
|
||||
video.resize = (state) => {
|
||||
if( video.resize.state == undefined ) video.resize.state = false
|
||||
if( state == undefined ) state = (video.resize.state = !video.resize.state )
|
||||
video.style.width = state ? '320px' : '80px'
|
||||
video.style.height = state ? '200px' : '60px'
|
||||
}
|
||||
|
||||
video.addEventListener('click', () => video.resize() )
|
||||
|
||||
this.peerVideos[peerId] = video
|
||||
return video
|
||||
},
|
||||
|
||||
createElement: function(str){
|
||||
let el = document.createElement("div")
|
||||
el.className = "msg"
|
||||
el.innerHTML = this.linkify(str)
|
||||
return el
|
||||
},
|
||||
|
||||
// central function to broadcast stuff to chat
|
||||
send: function(str){
|
||||
if( !this.sendChat ) return
|
||||
this.sendChat({content:str}) // send to network
|
||||
this.chat.append(str) // send to screen
|
||||
},
|
||||
|
||||
initChat: function(){
|
||||
|
||||
let chatline = this.chatline = document.querySelector("#chatline")
|
||||
let chat = this.chat = document.querySelector("#chat")
|
||||
chat.log = [] // save raw chatlog to prime new visitors
|
||||
chat.append = (str) => {
|
||||
if( !str ) return
|
||||
str = str.replace('\n', "<br>")
|
||||
this.chat.appendChild(this.createElement(str)) // send to screen
|
||||
this.chat.innerHTML += '<br>'
|
||||
this.chat.log.push(str)
|
||||
}
|
||||
|
||||
let send = () => {
|
||||
let str = `${this.idsToNames[ this.room.selfId ]}: ${chatline.value.substr(0,65515).trim()}`
|
||||
this.send(str)
|
||||
}
|
||||
|
||||
chatline.addEventListener("keydown", (e) => {
|
||||
if( e.key !== "Enter" ) return
|
||||
send()
|
||||
chatline.value = ''
|
||||
})
|
||||
|
||||
// listen for chatmsg
|
||||
this.getChat((data, peerId) => {
|
||||
if( data.prime ){
|
||||
if( this.chat.primed ) return // only prime once
|
||||
console.log("receiving prime")
|
||||
data.prime.map( (l) => chat.append(l) ) // send log to screen
|
||||
this.chat.primed = true
|
||||
}
|
||||
chat.append(data.content) // send to screen
|
||||
})
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
linkify: function(t){
|
||||
const isValidHttpUrl = s => {
|
||||
let u
|
||||
try {u = new URL(s)}
|
||||
catch (_) {return false}
|
||||
return u.protocol.startsWith("http")
|
||||
}
|
||||
const m = t.match(/(?<=\s|^)[a-zA-Z0-9-:/]+\.[a-zA-Z0-9-].+?(?=[.,;:?!-]?(?:\s|$))/g)
|
||||
if (!m) return t
|
||||
const a = []
|
||||
m.forEach(x => {
|
||||
const [t1, ...t2] = t.split(x)
|
||||
a.push(t1)
|
||||
t = t2.join(x)
|
||||
const y = (!(x.match(/:\/\//)) ? 'https://' : '') + x
|
||||
if (isNaN(x) && isValidHttpUrl(y))
|
||||
a.push('<a href="' + y + '" target="_blank" style="pointer-events:all">' + y.split('/')[2] + '</a>')
|
||||
else
|
||||
a.push(x)
|
||||
})
|
||||
a.push(t)
|
||||
return a.join('')
|
||||
}
|
||||
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue