initial commit

This commit is contained in:
Franz Heinzmann (Frando) 2021-06-07 16:22:13 +02:00
commit 0024f44c80
34 changed files with 1991 additions and 0 deletions

125
backend/server.mjs Normal file
View file

@ -0,0 +1,125 @@
import fastify from 'fastify'
import debug from 'debug'
import { EventEmitter } from 'events'
import p from 'path'
import { fileURLToPath } from 'url'
import { spawn } from 'child_process'
import minimist from 'minimist'
import { Transform, Readable } from 'streamx'
import fastifyStatic from 'fastify-static'
import fastifyCors from 'fastify-cors'
import fastifySSE from 'fastify-sse-v2'
import { Darkice } from './darkice.mjs'
import { Baresip } from './baresip.mjs'
import { AlsaMeter } from './lib/ffmpeg-meter.mjs'
import { ConnectivityCheck } from './lib/is-online.mjs'
import { reducer, action, CONNECTION_STATE, STATE_RESET, PING, METER, INITIAL_STATE } from './state.mjs'
const __dirname = p.dirname(fileURLToPath(import.meta.url))
function deviceToFFMPEG (device) {
const [host, ...rest] = device.split(':')
return { host, device: rest.join(':') }
}
export async function run (config = {}) {
const app = fastify()
const darkice = new Darkice()
const baresip = new Baresip()
const isOnline = new ConnectivityCheck()
const meter = new AlsaMeter({
input: deviceToFFMPEG(config.input)
})
meter.open().catch(console.error)
const actions = new Readable()
actions.setMaxListeners(1000)
const state = {
current: INITIAL_STATE,
dispatch: action => (state.current = reducer(state.current, action)),
}
actions.on('data', action => {
state.dispatch(action)
// console.log('action', action)
})
darkice.stream.on('data', action => actions.push(action))
baresip.stream.on('data', action => actions.push(action))
isOnline.on('data', action => actions.push(action))
meter.on('meter', data => actions.push(action(METER, data)))
const streams = { darkice, baresip }
// const state = new StateContainer(reducer, INITIAL_STATE)
// state.ingestActionStream(actions)
app.register(fastifyStatic, {
root: p.join(__dirname, '..', 'frontend', 'dist'),
prefix: '/'
})
app.register(fastifyCors, {
origin: '*'
})
app.register(fastifySSE)
app.get('/sse', async (req, reply) => {
console.log('sse req')
const stream = new Transform({
transform (action, cb) {
if (!this._id) this._id = 0
this.push({ data: JSON.stringify(action), id: ++this._id })
cb()
}
})
process.nextTick(() => {
stream.write(action(STATE_RESET, state.current))
stream.write(action(CONNECTION_STATE, { connected: true }))
actions.on('data', action => stream.write(action))
})
reply.sse(stream)
})
class InvalidIdError extends Error {
constructor (message, statusCode) {
super(message || 'Invalid Stream ID')
this.statusCode = statusCode || 400
}
}
app.post('/command', async (req, reply) => {
const { command, args } = req.body
const { id } = args
if (!streams[id]) throw new InvalidIdError()
const handler = streams[id]
switch (command) {
case 'start':
await handler.start()
break
case 'stop':
await handler.stop()
break
case 'restart':
await handler.restart()
break
}
return {
id,
error: false
}
})
app.setErrorHandler(function (error, request, reply) {
console.error('error', error)
if (!error || typeof error !== 'object') error = { message: error }
reply.code(error.statusCode || 500)
reply.send({ error: error.message || 'Internal server error' })
})
await app.listen(config.port, config.host)
console.log(`Server running at http://${config.host}:${config.port}/`)
}