vybe/client/stream.js

175 lines
4.6 KiB
JavaScript

import { render, html } from '/uhtml.js';
let mediaStream;
async function stream() {
let thread = window.currentThread;
if (thread.handle) {
clearInterval(thread.handle);
delete thread.handle;
if (thread.recorder.state === 'recording')
thread.recorder.stop();
thread.instance.emit('stream', {
id: thread.streamid,
thread: thread.id,
stop: true
});
document.getElementById('streaming').innerText = 'start streaming';
return;
}
if (!mediaStream)
mediaStream = await navigator.mediaDevices.getUserMedia({
audio: {
autoGainControl: false,
echoCancellation: false,
noiseSuppression: false,
sampleRate: 48000,
sampleSize: 16
}
});
if (!mediaStream) {
console.log("couldn't get media stream");
return;
}
thread.instance.emit('stream', {
thread: thread.id,
name: document.getElementById('streamname').value
}, msg => {
if (!msg.success) {
console.log('stream failed:', msg.message);
return;
}
thread.streamid = msg.id;
document.getElementById('streaming').innerText = 'stop streaming';
function record() {
let r = thread.recorder = new MediaRecorder(mediaStream);
let chunks = [];
r.ondataavailable = event => {
if (!event.data.size)
return;
chunks.push(event.data);
};
r.onstop = async () => {
if (!chunks.length || !thread.handle)
return;
//console.log(`${Date.now()} ${chunks.length}`);
thread.instance.emit('streamdata', {
id: thread.streamid,
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);
});
}
let audioctx;
function loadStreams() {
let instance = window.currentInstance;
let div = document.getElementById('stream');
div.innerHTML = '';
if (window.currentThread.permissions.post) {
// why doesn't html` work here? html.node` does
render(div, html.node`
<button id='streaming' onclick=${stream}>
${window.currentThread.handle ? 'stop' : 'start'} streaming
</button>
<span>stream name:</span>
<input id='streamname' oninput=${function(event) {
if (window.currentThread.handle)
instance.emit('stream', {
id: window.currentThread.streamid,
thread: window.currentThread.id,
name: this.value
});
}}>`);
}
div.insertAdjacentHTML('beforeend', `
<p>streams:</p>
<div id='streams'></div>`);
function addStream(stream) {
let p = html.node`
<p>
<button id='play' onclick=${e => {
if (stream.playing) {
audioctx.suspend();
delete instance.streaming[stream.id];
stream.playing = false;
e.target.innerText = '▶';
}
else {
audioctx = new AudioContext();
instance.streaming[stream.id] = stream;
stream.playing = true;
e.target.innerText = '⏹';
}
instance.emit('play_stream', {
id: stream.id,
thread: window.currentThread.id,
playing: stream.playing
}, msg => {
if (!msg.success)
console.log('play stream failed: ', msg.message);
});
}}>${instance.streaming[stream.id] ? '⏹' : '▶'}</button>
${stream.user}<span id='name'>${stream.name ? ` - ${stream.name}` : ''}</span>
</p>`;
p.id = 'stream' + stream.id;
document.getElementById('streams').append(p);
}
if (!instance.streaming) {
instance.streaming = {};
instance.socket.on('stream', async msg => {
let streams = document.querySelector(
`#instance${instance.id} > #threads > #threadlist > #thread${msg.thread}`)
.thread.streams;
let i = streams.findIndex(s => s.id === msg.id);
let p = document.getElementById('stream' + msg.id);
if (msg.stopped) {
if (i !== -1) {
streams.splice(i, 1);
if (msg.thread === window.currentThread?.id)
p.remove();
}
}
else if (i === -1) {
streams.push(msg);
if (msg.thread === window.currentThread?.id)
addStream(msg);
}
else {
streams[i].name = msg.name;
if (msg.thread === window.currentThread?.id)
p.children['name'].innerText = msg.name ? ' - ' + msg.name : '';
}
});
instance.socket.on('streamdata', async msg => {
if (!instance.streaming[msg.id])
return;
let source = audioctx.createBufferSource();
source.buffer = await audioctx.decodeAudioData(msg.audio);
source.connect(audioctx.destination);
source.start(/*audioStartTime*/);
});
}
for (let stream of window.currentThread.streams)
addStream(stream);
}
export default loadStreams;