streams, message time, space zoom+pan
parent
bb31b56929
commit
779aaece08
|
@ -162,6 +162,7 @@
|
||||||
}
|
}
|
||||||
> .title {
|
> .title {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding-inline: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.expander {
|
.expander {
|
||||||
|
@ -301,6 +302,12 @@
|
||||||
.message {
|
.message {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.time {
|
||||||
|
color: #999;
|
||||||
|
font-size: small;
|
||||||
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
#loadmore {
|
#loadmore {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -321,9 +328,12 @@
|
||||||
}
|
}
|
||||||
#space {
|
#space {
|
||||||
margin: -2px; /* offset column margin */
|
margin: -2px; /* offset column margin */
|
||||||
position: relative;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
#spacediv {
|
||||||
|
position: relative;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
}
|
||||||
.span {
|
.span {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -1,45 +1,56 @@
|
||||||
import { render, html } from '/uhtml.js';
|
import { render, html } from '/uhtml.js';
|
||||||
|
|
||||||
let msg;
|
let msg;
|
||||||
|
|
||||||
function sendMessage(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
if (!msg.value)
|
|
||||||
return;
|
|
||||||
window.currentInstance.emit('send_message', {
|
|
||||||
message: msg.value,
|
|
||||||
thread: window.currentThread.id
|
|
||||||
});
|
|
||||||
msg.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
let earliestMessage;
|
let earliestMessage;
|
||||||
|
|
||||||
function loadMessages(callback) {
|
function loadMessages(callback) {
|
||||||
let instance = window.currentInstance;
|
let instance = window.currentInstance;
|
||||||
|
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
render(document.getElementById('message'), html`
|
render(document.getElementById('message'), html`
|
||||||
<button id='loadmore' class='hidden' onclick=${loadMessages}>
|
<button id='loadmore' class='hidden' onclick=${loadMessages}>
|
||||||
load more messages
|
load more messages
|
||||||
</button>
|
</button>
|
||||||
<div id='messages'></div>
|
<div id='messages'></div>
|
||||||
<form id='msginput' onsubmit=${sendMessage}>
|
<form id='msginput' onsubmit=${function(event) {
|
||||||
<input id='msg' placeholder='write a message...' />
|
event.preventDefault();
|
||||||
<button type='submit' id='sendmsg'>send</button>
|
if (!msg.value)
|
||||||
</form>
|
return;
|
||||||
`);
|
window.currentInstance.emit('send_message', {
|
||||||
msg = document.getElementById('msg');
|
message: msg.value,
|
||||||
}
|
thread: window.currentThread.id
|
||||||
const messages = document.getElementById('messages');
|
});
|
||||||
if (!this) { // called from chooseThread, initializing thread
|
msg.value = '';
|
||||||
messages.innerHTML = '';
|
}}>
|
||||||
earliestMessage = null;
|
<input id='msg' placeholder='write a message...' />
|
||||||
|
<button type='submit' id='sendmsg'>send</button>
|
||||||
|
</form>
|
||||||
|
`);
|
||||||
|
msg = document.getElementById('msg');
|
||||||
|
}
|
||||||
|
const messages = document.getElementById('messages');
|
||||||
|
if (!this) { // called from chooseThread, initializing thread
|
||||||
|
messages.innerHTML = '';
|
||||||
|
earliestMessage = null;
|
||||||
if (window.currentThread.permissions.post)
|
if (window.currentThread.permissions.post)
|
||||||
document.getElementById('msginput').classList.remove('hidden');
|
document.getElementById('msginput').classList.remove('hidden');
|
||||||
else
|
else
|
||||||
document.getElementById('msginput').classList.add('hidden');
|
document.getElementById('msginput').classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addMessage(message, user) {
|
||||||
|
let now = new Date(), date = new Date(message.created);
|
||||||
|
let div = html.node`<div class='message'><span class='time'>${
|
||||||
|
now.getDate() === date.getDate() && date.getTime() > now.getTime() - 24 * 60 * 60000
|
||||||
|
? date.toLocaleTimeString() : date.toLocaleString()
|
||||||
|
}</span></div>`;
|
||||||
|
let content = html.node`<span class='content'>: ${message.content}</span>`;
|
||||||
|
content.prepend(user ? window.makeUser(user, user.id.split?.('@')[1] || instance.url)
|
||||||
|
: html.node`<span>${message.user.id}</span>`);
|
||||||
|
div.prepend(content);
|
||||||
|
messages.append(div);
|
||||||
|
}
|
||||||
|
|
||||||
instance.emit('get_history', {
|
instance.emit('get_history', {
|
||||||
before: earliestMessage,
|
before: earliestMessage,
|
||||||
thread: window.currentThread.id
|
thread: window.currentThread.id
|
||||||
|
@ -73,10 +84,7 @@ function loadMessages(callback) {
|
||||||
user.permissions = message.user.permissions;
|
user.permissions = message.user.permissions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let div = html.node`<div class='message'>: ${message.content}</div>`;
|
addMessage(message, user);
|
||||||
div.prepend(user ? window.makeUser(user, user.id.split?.('@')[1] || instance.url)
|
|
||||||
: html.node`<span>${message.user.id}</span>`);
|
|
||||||
messages.prepend(div);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (msg.more)
|
if (msg.more)
|
||||||
|
@ -92,10 +100,7 @@ function loadMessages(callback) {
|
||||||
return;
|
return;
|
||||||
const messages = document.getElementById('messages');
|
const messages = document.getElementById('messages');
|
||||||
let scroll = messages.scrollTop + 10 >= messages.scrollHeight - messages.clientHeight;
|
let scroll = messages.scrollTop + 10 >= messages.scrollHeight - messages.clientHeight;
|
||||||
let div = html.node`<div class='message'>: ${message.content}</div>`;
|
addMessage(message, message.user);
|
||||||
div.prepend(window.makeUser(message.user,
|
|
||||||
message.user.id.split?.('@')[1] || instance.url));
|
|
||||||
messages.append(div);
|
|
||||||
if (scroll)
|
if (scroll)
|
||||||
messages.scroll(0, messages.scrollHeight - messages.clientHeight);
|
messages.scroll(0, messages.scrollHeight - messages.clientHeight);
|
||||||
if (!earliestMessage)
|
if (!earliestMessage)
|
||||||
|
|
|
@ -1,23 +1,30 @@
|
||||||
let space;
|
let spaceContainer, space;
|
||||||
|
|
||||||
let scale = 1; // todo: make zooming work
|
|
||||||
|
|
||||||
let editing;
|
let editing;
|
||||||
let dragging;
|
let dragging;
|
||||||
let moved;
|
let moved;
|
||||||
let movedFrom;
|
let movedFrom;
|
||||||
let offset;
|
let offset;
|
||||||
|
let clicked;
|
||||||
|
|
||||||
document.onmousemove = event => {
|
document.addEventListener('mouseup', function(event) {
|
||||||
|
clicked = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', function(event) {
|
||||||
moved = true;
|
moved = true;
|
||||||
if (!dragging)
|
if (dragging) {
|
||||||
return;
|
let left = event.clientX - spaceContainer.offsetLeft - offset.x;
|
||||||
let left = (event.clientX - space.offsetLeft) * scale - offset.x;
|
let top = event.clientY - spaceContainer.offsetTop - offset.y;
|
||||||
let top = (event.clientY - space.offsetTop) * scale - offset.y;
|
dragging.style.left = `${left < 0 ? 0 : left}px`;
|
||||||
dragging.style.left = `${left < 0 ? 0 : left}px`;
|
dragging.style.top = `${top < 0 ? 0 : top}px`;
|
||||||
dragging.style.top = `${top < 0 ? 0 : top}px`;
|
save(dragging);
|
||||||
save(dragging);
|
}
|
||||||
};
|
else if (clicked) {
|
||||||
|
spaceContainer.scrollLeft -= event.movementX;
|
||||||
|
spaceContainer.scrollTop -= event.movementY;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let saving;
|
let saving;
|
||||||
let queue;
|
let queue;
|
||||||
|
@ -79,24 +86,28 @@ function add(s) {
|
||||||
};
|
};
|
||||||
span.onwheel = function(event) {
|
span.onwheel = function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (event.deltaY < 0 && this.scale >= 200)
|
event.stopPropagation();
|
||||||
return;
|
|
||||||
this.scale *= 1 - event.deltaY * .001;
|
this.scale *= 1 - event.deltaY * .001;
|
||||||
|
if (this.scale > 100)
|
||||||
|
this.scale = 100;
|
||||||
this.style.transform = `translate(-50%, -50%) scale(${this.scale})`;
|
this.style.transform = `translate(-50%, -50%) scale(${this.scale})`;
|
||||||
save(this);
|
save(this);
|
||||||
};
|
};
|
||||||
span.onmousedown = function(event) {
|
span.onmousedown = function(event) {
|
||||||
if (dragging || editing === this)
|
if (dragging || editing === this)
|
||||||
return;
|
return;
|
||||||
dragging = this;
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
if (event.button !== 0)
|
||||||
|
return;
|
||||||
|
dragging = this;
|
||||||
offset = {
|
offset = {
|
||||||
x: event.clientX - (space.offsetLeft + this.offsetLeft),
|
x: event.clientX - (spaceContainer.offsetLeft + this.offsetLeft),
|
||||||
y: event.clientY - (space.offsetTop + this.offsetTop)
|
y: event.clientY - (spaceContainer.offsetTop + this.offsetTop)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
span.onmouseup = function(event) {
|
span.onmouseup = function(event) {
|
||||||
event.stopPropagation();
|
if (event.button !== 0)
|
||||||
|
return;
|
||||||
dragging = null;
|
dragging = null;
|
||||||
if (moved)
|
if (moved)
|
||||||
return;
|
return;
|
||||||
|
@ -109,14 +120,36 @@ function add(s) {
|
||||||
|
|
||||||
export default function loadSpace(callback) {
|
export default function loadSpace(callback) {
|
||||||
let instance = window.currentInstance;
|
let instance = window.currentInstance;
|
||||||
|
|
||||||
if (!instance.spaceid) {
|
let scale = 1;
|
||||||
space = document.getElementById('space');
|
|
||||||
space.onmousedown = event => {
|
if (!instance.spaced) {
|
||||||
|
spaceContainer = document.getElementById('space');
|
||||||
|
space = document.getElementById('spacediv');
|
||||||
|
|
||||||
|
spaceContainer.onwheel = function(event) {
|
||||||
|
if (event.getModifierState('Shift'))
|
||||||
|
return;
|
||||||
|
event.preventDefault();
|
||||||
|
scale *= 1 - event.deltaY * .001;
|
||||||
|
if (scale < .5)
|
||||||
|
scale = .5;
|
||||||
|
space.style.transform = `scale(${scale})`;
|
||||||
|
spaceContainer.scrollLeft += event.offsetX * (scale - scale / (1 - event.deltaY * .001));
|
||||||
|
spaceContainer.scrollTop += event.offsetY * (scale - scale / (1 - event.deltaY * .001));
|
||||||
|
//spaceContainer.scrollLeft += spaceContainer.clientWidth * (1 - spaceContainer.clientWidth / spaceContainer.scrollWidth + event.offsetX / spaceContainer.clientWidth) * (scale - scale / (1 - event.deltaY * .001));
|
||||||
|
//spaceContainer.scrollTop += spaceContainer.clientHeight * (1 - spaceContainer.clientHeight / spaceContainer.scrollHeight + event.offsetY / spaceContainer.clientHeight) * (scale - scale / (1 - event.deltaY * .001));
|
||||||
|
};
|
||||||
|
spaceContainer.onmousedown = function(event) {
|
||||||
|
if (event.button !== 0)
|
||||||
|
return;
|
||||||
|
clicked = true;
|
||||||
moved = false;
|
moved = false;
|
||||||
movedFrom = { x: event.offsetX, y: event.offsetY };
|
movedFrom = { x: event.offsetX, y: event.offsetY };
|
||||||
};
|
};
|
||||||
space.onmouseup = event => {
|
spaceContainer.onmouseup = function(event) {
|
||||||
|
if (event.button !== 0)
|
||||||
|
return;
|
||||||
if (dragging) {
|
if (dragging) {
|
||||||
dragging.onmouseup(event);
|
dragging.onmouseup(event);
|
||||||
return;
|
return;
|
||||||
|
@ -130,8 +163,8 @@ export default function loadSpace(callback) {
|
||||||
+ (event.offsetY - movedFrom.y) * (event.offsetY - movedFrom.y) > 100)
|
+ (event.offsetY - movedFrom.y) * (event.offsetY - movedFrom.y) > 100)
|
||||||
return;
|
return;
|
||||||
editing = add({
|
editing = add({
|
||||||
x: event.offsetX + space.scrollLeft,
|
x: (event.offsetX + spaceContainer.scrollLeft) / scale,
|
||||||
y: event.offsetY + space.scrollTop,
|
y: (event.offsetY + spaceContainer.scrollTop) / scale,
|
||||||
scale: 1,
|
scale: 1,
|
||||||
content: ''
|
content: ''
|
||||||
});
|
});
|
||||||
|
@ -139,7 +172,7 @@ export default function loadSpace(callback) {
|
||||||
};
|
};
|
||||||
|
|
||||||
instance.socket.on('span', msg => {
|
instance.socket.on('span', msg => {
|
||||||
if (msg.thread !== instance.spaceid || window.currentInstance !== instance)
|
if (msg.thread !== window.currentSpace.id || window.currentInstance !== instance)
|
||||||
return;
|
return;
|
||||||
let span = document.getElementById('span' + msg.id);
|
let span = document.getElementById('span' + msg.id);
|
||||||
if (span) {
|
if (span) {
|
||||||
|
@ -152,13 +185,15 @@ export default function loadSpace(callback) {
|
||||||
else
|
else
|
||||||
add(msg);
|
add(msg);
|
||||||
});
|
});
|
||||||
|
instance.spaced = true;
|
||||||
}
|
}
|
||||||
else if (instance.spaceid === window.currentThread.id)
|
else if (window.currentSpace === window.currentThread)
|
||||||
return;
|
return;
|
||||||
instance.spaceid = window.currentThread.id;
|
window.currentSpace = window.currentThread;
|
||||||
space.innerHTML = '';
|
space.innerHTML = '';
|
||||||
|
space.style.transform = '';
|
||||||
instance.emit('get_space', {
|
instance.emit('get_space', {
|
||||||
thread: window.currentThread.id
|
thread: window.currentSpace.id
|
||||||
}, msg => {
|
}, msg => {
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
console.log('get space failed: ' + msg.message);
|
console.log('get space failed: ' + msg.message);
|
||||||
|
|
111
client/stream.js
111
client/stream.js
|
@ -2,8 +2,7 @@ import { render, html } from '/uhtml.js';
|
||||||
|
|
||||||
let mediaStream;
|
let mediaStream;
|
||||||
|
|
||||||
async function stream() {
|
async function stream(thread) {
|
||||||
let thread = window.currentThread;
|
|
||||||
if (thread.handle) {
|
if (thread.handle) {
|
||||||
clearInterval(thread.handle);
|
clearInterval(thread.handle);
|
||||||
delete thread.handle;
|
delete thread.handle;
|
||||||
|
@ -35,40 +34,40 @@ async function stream() {
|
||||||
thread.instance.emit('stream', {
|
thread.instance.emit('stream', {
|
||||||
thread: thread.id,
|
thread: thread.id,
|
||||||
name: document.getElementById('streamname').value
|
name: document.getElementById('streamname').value
|
||||||
}, msg => {
|
}, async msg => {
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
console.log('stream failed:', msg.message);
|
console.log('stream failed:', msg.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
thread.streamid = msg.id;
|
thread.streamid = msg.id;
|
||||||
document.getElementById('streaming').innerText = 'stop streaming';
|
document.getElementById('streaming').innerText = 'stop streaming';
|
||||||
function record() {
|
thread.recorder = new MediaRecorder(mediaStream, {
|
||||||
let r = thread.recorder = new MediaRecorder(mediaStream);
|
mimeType: 'audio/webm;codecs=opus'
|
||||||
let chunks = [];
|
});
|
||||||
r.ondataavailable = event => {
|
thread.recorder.start();
|
||||||
if (!event.data.size)
|
thread.recorder.ondataavailable = async event => {
|
||||||
|
if (!event.data.size || !thread.handle)
|
||||||
|
return;
|
||||||
|
thread.instance.emit('streamdata', {
|
||||||
|
id: thread.streamid,
|
||||||
|
audio: await event.data.arrayBuffer()
|
||||||
|
}, msg => {
|
||||||
|
if (msg.success)
|
||||||
return;
|
return;
|
||||||
chunks.push(event.data);
|
console.log('streamdata failed:', msg.message);
|
||||||
};
|
if (msg.message === 'stream not found' && thread.handle)
|
||||||
r.onstop = async () => {
|
stream(thread);
|
||||||
if (!chunks.length || !thread.handle)
|
});
|
||||||
|
};
|
||||||
|
// first 200ms chunk will be used as stream header
|
||||||
|
thread.handle = setTimeout(() => {
|
||||||
|
thread.recorder.requestData();
|
||||||
|
thread.handle = setInterval(() => {
|
||||||
|
if (!thread.handle)
|
||||||
return;
|
return;
|
||||||
//console.log(`${Date.now()} ${chunks.length}`);
|
thread.recorder.requestData();
|
||||||
thread.instance.emit('streamdata', {
|
}, 500);
|
||||||
id: thread.streamid,
|
}, 200);
|
||||||
audio: await (new Blob(chunks, { type: chunks[0].type })).arrayBuffer()
|
|
||||||
}, msg => {
|
|
||||||
if (!msg.success)
|
|
||||||
console.log('streamdata failed:', msg.message);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
r.onstart = () => {
|
|
||||||
setTimeout(() => r.state === 'recording' && r.stop(), 500);
|
|
||||||
};
|
|
||||||
r.start();
|
|
||||||
}
|
|
||||||
record();
|
|
||||||
thread.handle = setInterval(record, 500);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,24 +78,24 @@ function loadStreams() {
|
||||||
div.innerHTML = '';
|
div.innerHTML = '';
|
||||||
if (window.currentThread?.permissions.post) {
|
if (window.currentThread?.permissions.post) {
|
||||||
render(div, html.node`
|
render(div, html.node`
|
||||||
<button id='streaming' onclick=${stream}>
|
<button id='streaming' onclick=${function(event){
|
||||||
${window.currentThread.handle ? 'stop' : 'start'} streaming
|
stream(currentThread);
|
||||||
</button>
|
}}>${currentThread.handle ? 'stop' : 'start'} streaming</button>
|
||||||
<span>stream name:</span>
|
<span>stream name:</span>
|
||||||
<input id='streamname' oninput=${function(event) {
|
<input id='streamname' oninput=${function(event) {
|
||||||
if (window.currentThread.handle)
|
if (currentThread.handle)
|
||||||
instance.emit('stream', {
|
instance.emit('stream', {
|
||||||
id: window.currentThread.streamid,
|
id: currentThread.streamid,
|
||||||
thread: window.currentThread.id,
|
thread: currentThread.id,
|
||||||
name: this.value
|
name: this.value
|
||||||
});
|
});
|
||||||
window.currentThread.streamname = this.value;
|
currentThread.streamname = this.value;
|
||||||
}}>
|
}}>
|
||||||
<p id='listeners'>${window.currentThread.listeners ?
|
<p id='listeners'>${currentThread.listeners ?
|
||||||
window.currentThread.listeners + ' listeners' : ''}
|
currentThread.listeners + ' listeners' : ''}
|
||||||
</p>`);
|
</p>`);
|
||||||
if (window.currentThread.streamname)
|
if (currentThread.streamname)
|
||||||
document.getElementById('streamname').value = window.currentThread.streamname;
|
document.getElementById('streamname').value = currentThread.streamname;
|
||||||
}
|
}
|
||||||
div.insertAdjacentHTML('beforeend', `
|
div.insertAdjacentHTML('beforeend', `
|
||||||
<p>streams:</p>
|
<p>streams:</p>
|
||||||
|
@ -107,16 +106,23 @@ function loadStreams() {
|
||||||
<p>
|
<p>
|
||||||
<button id='play' onclick=${function(event) {
|
<button id='play' onclick=${function(event) {
|
||||||
if (stream.playing) {
|
if (stream.playing) {
|
||||||
stream.audioctx.suspend();
|
stream.audio.pause();
|
||||||
delete instance.streaming[stream.id];
|
delete instance.streaming[stream.id];
|
||||||
stream.playing = false;
|
stream.playing = false;
|
||||||
this.innerText = '▶';
|
this.innerText = '▶';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
stream.audioctx = new AudioContext();
|
stream.audio = new Audio();
|
||||||
stream.gain = stream.audioctx.createGain();
|
// play 200ms stream header silently
|
||||||
stream.gain.connect(stream.audioctx.destination);
|
stream.audio.volume = 0;
|
||||||
stream.gain.gain.value = p.children['volume'].value / 100;
|
stream.audio.onplaying = () => setTimeout(() => {
|
||||||
|
stream.audio.volume = p.children['volume'].value / 100;
|
||||||
|
p.children['volume'].oninput = function(event) {
|
||||||
|
stream.audio.volume = this.value / 100;
|
||||||
|
};
|
||||||
|
}, 200);
|
||||||
|
stream.audio.src = '/stream/' + stream.id;
|
||||||
|
stream.audio.play();
|
||||||
instance.streaming[stream.id] = stream;
|
instance.streaming[stream.id] = stream;
|
||||||
stream.playing = true;
|
stream.playing = true;
|
||||||
this.innerText = '⏹';
|
this.innerText = '⏹';
|
||||||
|
@ -127,13 +133,10 @@ function loadStreams() {
|
||||||
playing: stream.playing
|
playing: stream.playing
|
||||||
}, msg => {
|
}, msg => {
|
||||||
if (!msg.success)
|
if (!msg.success)
|
||||||
console.log('play stream failed: ', msg.message);
|
console.log('play_stream failed: ', msg.message);
|
||||||
});
|
});
|
||||||
}}>${instance.streaming[stream.id] ? '⏹' : '▶'}</button>
|
}}>${instance.streaming[stream.id] ? '⏹' : '▶'}</button>
|
||||||
<input id='volume' type='range' oninput=${function(event) {
|
<input id='volume' type='range'>
|
||||||
if (stream.gain)
|
|
||||||
stream.gain.gain.value = this.value / 100;
|
|
||||||
}}>
|
|
||||||
<span id='name'>${stream.name ? ` - ${stream.name}` : ''}</span>
|
<span id='name'>${stream.name ? ` - ${stream.name}` : ''}</span>
|
||||||
</p>`;
|
</p>`;
|
||||||
p.insertBefore(window.makeUser(stream.user,
|
p.insertBefore(window.makeUser(stream.user,
|
||||||
|
@ -171,16 +174,6 @@ function loadStreams() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.socket.on('streamdata', async msg => {
|
|
||||||
let stream = instance.streaming[msg.id];
|
|
||||||
if (!stream)
|
|
||||||
return;
|
|
||||||
let source = stream.audioctx.createBufferSource();
|
|
||||||
source.buffer = await stream.audioctx.decodeAudioData(msg.audio);
|
|
||||||
source.connect(stream.gain);
|
|
||||||
source.start(/*audioStartTime*/);
|
|
||||||
});
|
|
||||||
|
|
||||||
instance.socket.on('listeners', msg => {
|
instance.socket.on('listeners', msg => {
|
||||||
document.querySelector(
|
document.querySelector(
|
||||||
`#instance${instance.id} > #threads > #threadlist > #thread${msg.thread}`)
|
`#instance${instance.id} > #threads > #threadlist > #thread${msg.thread}`)
|
||||||
|
|
|
@ -419,7 +419,9 @@ async function loadThreads(instancediv, select) {
|
||||||
}>members</button>
|
}>members</button>
|
||||||
</div>
|
</div>
|
||||||
<div id='message' class='tabcontent'></div>
|
<div id='message' class='tabcontent'></div>
|
||||||
<div id='space' class='tabcontent hidden'></div>
|
<div id='space' class='tabcontent hidden'>
|
||||||
|
<div id='spacediv'></div>
|
||||||
|
</div>
|
||||||
<div id='call' class='tabcontent hidden'></div>
|
<div id='call' class='tabcontent hidden'></div>
|
||||||
</div>
|
</div>
|
||||||
<hr class='separator' color='#505050'>
|
<hr class='separator' color='#505050'>
|
||||||
|
|
15
server.js
15
server.js
|
@ -4,6 +4,7 @@ const http = require('http');
|
||||||
const { Server } = require('socket.io');
|
const { Server } = require('socket.io');
|
||||||
const ioclient = require('socket.io-client');
|
const ioclient = require('socket.io-client');
|
||||||
const compression = require('compression');
|
const compression = require('compression');
|
||||||
|
const { PassThrough } = require('stream');
|
||||||
|
|
||||||
const events = {};
|
const events = {};
|
||||||
for (let file of fs.readdirSync('./src/events')) {
|
for (let file of fs.readdirSync('./src/events')) {
|
||||||
|
@ -89,6 +90,20 @@ const io = new Server(server, {
|
||||||
|
|
||||||
app.use(express.static('client'));
|
app.use(express.static('client'));
|
||||||
|
|
||||||
|
app.get('/stream/:id', (req, res) => {
|
||||||
|
let stream = vybe.streams[req.params.id];
|
||||||
|
if (!stream) {
|
||||||
|
res.sendStatus(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.status(200);
|
||||||
|
let passthrough = new PassThrough();
|
||||||
|
passthrough.pipe(res);
|
||||||
|
stream.streams.add(passthrough);
|
||||||
|
res.set('Content-Type', 'audio/webm');
|
||||||
|
req.on('close', () => stream.streams.delete(passthrough));
|
||||||
|
});
|
||||||
|
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', (socket) => {
|
||||||
for (let event in events) {
|
for (let event in events) {
|
||||||
socket.on(event, (msg, callback) => {
|
socket.on(event, (msg, callback) => {
|
||||||
|
|
|
@ -46,7 +46,8 @@ async function send_message(msg, respond) {
|
||||||
permissions: userperms
|
permissions: userperms
|
||||||
},
|
},
|
||||||
content: msg.message,
|
content: msg.message,
|
||||||
thread: msg.thread
|
thread: msg.thread,
|
||||||
|
created: Date.now()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,12 +78,12 @@ async function get_history(msg, respond) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const messages = (await db.query(`
|
const messages = (await db.query(`
|
||||||
select coalesce(displayname, name) as displayname, name, user as userid, post.id, content
|
select coalesce(displayname, name) as displayname, name, user as userid, post.id, content, post.created
|
||||||
from post
|
from post
|
||||||
left join user on post.user = user.id
|
left join user on post.user = user.id
|
||||||
where ${msg.before ? 'post.id < ? and' : ''}
|
where ${msg.before ? 'post.id < ? and' : ''}
|
||||||
thread = ?
|
thread = ?
|
||||||
order by post.created desc
|
order by post.created asc
|
||||||
limit 101`,
|
limit 101`,
|
||||||
msg.before ? [msg.before, msg.thread] : [msg.thread]
|
msg.before ? [msg.before, msg.thread] : [msg.thread]
|
||||||
)).rows;
|
)).rows;
|
||||||
|
@ -103,7 +104,8 @@ async function get_history(msg, respond) {
|
||||||
displayname: message.displayname,
|
displayname: message.displayname,
|
||||||
permissions: perms[message.userid]
|
permissions: perms[message.userid]
|
||||||
},
|
},
|
||||||
content: message.content
|
content: message.content,
|
||||||
|
created: new Date(message.created + 'Z').getTime()
|
||||||
})),
|
})),
|
||||||
more: messages.length > 100
|
more: messages.length > 100
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,24 +32,22 @@ async function stream(msg, respond, socket) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof msg.id === 'number') {
|
if (typeof msg.id === 'number') {
|
||||||
stream = vybe.streams[msg.id];
|
let vstream = vybe.streams[msg.id];
|
||||||
if (!stream)
|
if (!vstream)
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'stream not found'
|
message: 'stream not found'
|
||||||
});
|
});
|
||||||
if (msg.auth_user.id !== stream.userid)
|
if (msg.auth_user.id !== vstream.userid)
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: "stream doesn't belong to user"
|
message: "stream doesn't belong to user"
|
||||||
});
|
});
|
||||||
|
stream = vstream.stream;
|
||||||
if (msg.stop) {
|
if (msg.stop) {
|
||||||
await stream.stop();
|
await vstream.stop();
|
||||||
return respond({
|
return respond({ success: true });
|
||||||
success: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
stream = stream.stream;
|
|
||||||
stream.name = msg.name;
|
stream.name = msg.name;
|
||||||
stream.user.displayname = msg.auth_user.displayname;
|
stream.user.displayname = msg.auth_user.displayname;
|
||||||
}
|
}
|
||||||
|
@ -82,7 +80,8 @@ async function stream(msg, respond, socket) {
|
||||||
thread.streams.splice(thread.streams.findIndex(s => s.id === stream.id), 1);
|
thread.streams.splice(thread.streams.findIndex(s => s.id === stream.id), 1);
|
||||||
delete vybe.streams[stream.id];
|
delete vybe.streams[stream.id];
|
||||||
await send();
|
await send();
|
||||||
}
|
},
|
||||||
|
streams: new Set()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
await send();
|
await send();
|
||||||
|
@ -106,15 +105,11 @@ async function streamdata(msg, respond) {
|
||||||
message: "stream doesn't belong to user"
|
message: "stream doesn't belong to user"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (let id in stream.listeners)
|
if (stream.head)
|
||||||
stream.listeners[id].emit('streamdata', {
|
stream.streams.forEach(passthrough => passthrough.write(msg.audio));
|
||||||
id: msg.id,
|
else
|
||||||
audio: msg.audio,
|
stream.head = msg.audio;
|
||||||
video: msg.video
|
respond({ success: true });
|
||||||
});
|
|
||||||
respond({
|
|
||||||
success: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function play_stream(msg, respond, socket) {
|
async function play_stream(msg, respond, socket) {
|
||||||
|
@ -138,9 +133,7 @@ async function play_stream(msg, respond, socket) {
|
||||||
thread: stream.stream.thread,
|
thread: stream.stream.thread,
|
||||||
count: Object.keys(stream.listeners).length
|
count: Object.keys(stream.listeners).length
|
||||||
});
|
});
|
||||||
respond({
|
respond({ success: true });
|
||||||
success: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
Loading…
Reference in New Issue