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){
|
function setupUrlBar(el,XRF){
|
||||||
let inIframe = window.location !== window.parent.location
|
let inIframe = window.location !== window.parent.location
|
||||||
let ids = ['#overlay','a#embed','a#source','a#model','#qrcode']
|
let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode']
|
||||||
let showButtons = () => {
|
let showButtons = (state) => {
|
||||||
ids.map( (i) => $(i).style.display = 'block' )
|
ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' )
|
||||||
$('a#more').style.display = 'none'
|
$('a#more').style.display = state ? 'none' : 'inline-block'
|
||||||
if( inIframe ) $('#uri').style.display = '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() )
|
XRF.addEventListener('hash', () => reflectUrl() )
|
||||||
const reflectUrl = window.reflectUrl = (url) => {
|
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
|
* https://xrfragment.org
|
||||||
* SPDX-License-Identifier: MPL-2.0
|
* SPDX-License-Identifier: MPL-2.0
|
||||||
*/
|
*/
|
||||||
|
|
@ -2381,13 +2381,17 @@ function setupConsole(el){
|
||||||
|
|
||||||
function setupUrlBar(el,XRF){
|
function setupUrlBar(el,XRF){
|
||||||
let inIframe = window.location !== window.parent.location
|
let inIframe = window.location !== window.parent.location
|
||||||
let ids = ['#overlay','a#embed','a#source','a#model','#qrcode']
|
let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode']
|
||||||
let showButtons = () => {
|
let showButtons = (state) => {
|
||||||
ids.map( (i) => $(i).style.display = 'block' )
|
ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' )
|
||||||
$('a#more').style.display = 'none'
|
$('a#more').style.display = state ? 'none' : 'inline-block'
|
||||||
if( inIframe ) $('#uri').style.display = '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() )
|
XRF.addEventListener('hash', () => reflectUrl() )
|
||||||
const reflectUrl = window.reflectUrl = (url) => {
|
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
|
// look-controls turns off autoUpdateMatrix (of player) which
|
||||||
// will break teleporting and other stuff
|
// will break teleporting and other stuff
|
||||||
// overriding this is easier then adding updateMatrixWorld() everywhere else
|
// 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
|
* https://xrfragment.org
|
||||||
* SPDX-License-Identifier: MPL-2.0
|
* SPDX-License-Identifier: MPL-2.0
|
||||||
*/
|
*/
|
||||||
|
|
@ -2375,13 +2375,17 @@ function setupConsole(el){
|
||||||
|
|
||||||
function setupUrlBar(el,XRF){
|
function setupUrlBar(el,XRF){
|
||||||
let inIframe = window.location !== window.parent.location
|
let inIframe = window.location !== window.parent.location
|
||||||
let ids = ['#overlay','a#embed','a#source','a#model','#qrcode']
|
let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode']
|
||||||
let showButtons = () => {
|
let showButtons = (state) => {
|
||||||
ids.map( (i) => $(i).style.display = 'block' )
|
ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' )
|
||||||
$('a#more').style.display = 'none'
|
$('a#more').style.display = state ? 'none' : 'inline-block'
|
||||||
if( inIframe ) $('#uri').style.display = '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() )
|
XRF.addEventListener('hash', () => reflectUrl() )
|
||||||
const reflectUrl = window.reflectUrl = (url) => {
|
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
|
// look-controls turns off autoUpdateMatrix (of player) which
|
||||||
// will break teleporting and other stuff
|
// will break teleporting and other stuff
|
||||||
// overriding this is easier then adding updateMatrixWorld() everywhere else
|
// 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
|
* https://xrfragment.org
|
||||||
* SPDX-License-Identifier: MPL-2.0
|
* 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
|
* https://xrfragment.org
|
||||||
* SPDX-License-Identifier: MPL-2.0
|
* 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"/>
|
<input type="text" id="uri" value="" onchange="AFRAME.XRF.navigator.to( $('#uri').value )" style="display:none"/>
|
||||||
</div>
|
</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>
|
<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-scene xr-mode-ui="XRMode: xr" renderer="colorManagement: true; highRefreshRate:true" light="defaultLightsEnabled: false">
|
||||||
<a-entity id="player" wasd-controls look-controls>
|
<a-entity id="player" wasd-controls look-controls>
|
||||||
|
|
@ -96,5 +90,15 @@
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</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>
|
</body>
|
||||||
</html>
|
</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;
|
text-decoration:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.btn-foot#embed{
|
a.btn-foot#clone{
|
||||||
color: #888;
|
color: #888;
|
||||||
bottom: 129px;
|
bottom: 129px;
|
||||||
}
|
}
|
||||||
|
|
@ -133,14 +133,20 @@ a.btn-foot#more{
|
||||||
bottom:72px;
|
bottom:72px;
|
||||||
width: 37px;
|
width: 37px;
|
||||||
min-width: 37px;
|
min-width: 37px;
|
||||||
|
font-size:23px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.btn-foot#source{
|
a.btn-foot#embed{
|
||||||
bottom:186px;
|
bottom:186px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a#source,
|
a.btn-foot#meeting{
|
||||||
|
bottom:246px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a#meeting,
|
||||||
|
a#clone,
|
||||||
a#embed,
|
a#embed,
|
||||||
a#model{
|
a#model{
|
||||||
display:none
|
display:none
|
||||||
|
|
@ -217,9 +223,9 @@ html{
|
||||||
transition: all ease .5s;
|
transition: all ease .5s;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: 0 0 4px 0 #0007;
|
box-shadow: 0 0 4px 0 #0007;
|
||||||
left: 20px;
|
right: 20px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 20px;
|
top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.js-snackbar {
|
.js-snackbar {
|
||||||
|
|
@ -291,18 +297,15 @@ html{
|
||||||
|
|
||||||
#qrcode{
|
#qrcode{
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
position: absolute;
|
width: 111px;
|
||||||
border: 5px solid #1c1c3299;
|
|
||||||
top: 66px;
|
|
||||||
right: 20px;
|
|
||||||
width: 175px;
|
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 5px solid #888;
|
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
margin: 20px 20px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC);
|
-webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC);
|
||||||
|
bottom: 306px;
|
||||||
|
height: 121px;
|
||||||
|
right: 19px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input#share{
|
input#share{
|
||||||
|
|
|
||||||
|
|
@ -55,13 +55,17 @@ export function setupConsole(el){
|
||||||
|
|
||||||
export function setupUrlBar(el,XRF){
|
export function setupUrlBar(el,XRF){
|
||||||
let inIframe = window.location !== window.parent.location
|
let inIframe = window.location !== window.parent.location
|
||||||
let ids = ['#overlay','a#embed','a#source','a#model','#qrcode']
|
let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode']
|
||||||
let showButtons = () => {
|
let showButtons = (state) => {
|
||||||
ids.map( (i) => $(i).style.display = 'block' )
|
ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' )
|
||||||
$('a#more').style.display = 'none'
|
$('a#more').style.display = state ? 'none' : 'inline-block'
|
||||||
if( inIframe ) $('#uri').style.display = '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() )
|
XRF.addEventListener('hash', () => reflectUrl() )
|
||||||
const reflectUrl = window.reflectUrl = (url) => {
|
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