ffmpeg-service/public/assets/app.js

296 lines
9.5 KiB
JavaScript

class Mitt {
constructor() {
this.all = new Map()
}
on(type, handler) {
var group = this.all.get(type)
group ? group.push(handler) : this.all.set(type, [handler])
}
off(type, handler) {
var group = this.all.get(type)
group && (handler ? group.splice(group.indexOf(handler) >>> 0, 1) : group.set(type, []))
}
emit(type, payload) {
var group = this.all.get(type)
if (group) { group.forEach(x => x(payload)) }
if (group = this.all.get('*')) {
group.forEach(x => x(type, payload))
}
}
}
class Animator {
constructor() {
this.running = null
this.runningInterval = null
}
begin(time) {
if (this.runningInterval === time && this.running) return
if (this.running) {
clearInterval(this.running)
}
this.runningInterval = time
this.running = setInterval(function() {
m.redraw()
}, time)
}
stop() {
clearInterval(this.running)
this.running = null
}
}
const animator = new Animator()
class WSClient extends Mitt
{
constructor(url = 'ws://localhost:4040') {
super()
this.url = url
this.socket = null
this.connected = false
this.connecting = null
this.connectingAt = null
this.reconnecting = null
this.reconnectingAt = null
this.retryDuration = 1000
this.open()
}
open() {
if (this.connected || this.connecting) return
clearTimeout(this.reconnecting)
this.reconnecting = this.reconnectingAt = null
this.connectingAt = new Date()
this.socket = new WebSocket(this.url)
this.socket.addEventListener('open', this.onopen.bind(this))
this.socket.addEventListener('message', this.onmessage.bind(this))
this.socket.addEventListener('error', this.onerror.bind(this))
this.socket.addEventListener('close', this.onclose.bind(this))
animator.begin(1000)
this.connecting = setTimeout(() => {
this.socket.close()
}, 5000)
m.redraw()
}
onopen() {
animator.stop()
this.retryDuration = 1000
clearTimeout(this.connecting)
this.connecting = null
this.connectingAt = null
this.reconnectingAt = null
this.connected = true
// this.send('onopen', { msg: 'Hello Server!' })
m.redraw()
}
send(type, payload) {
if (!this.connected) return
this.socket.send(JSON.stringify({
type: type,
payload: payload,
}))
}
onerror() {
this.reconnect()
}
onclose() {
this.reconnect()
}
reconnect() {
if (this.reconnectingAt) return
animator.begin(100)
this.socket = null
this.connected = false
clearTimeout(this.connecting)
this.connecting = null
this.connectingAt = null
this.reconnectingAt = new Date(new Date().getTime() + this.retryDuration)
clearTimeout(this.reconnecting)
this.reconnecting = setTimeout(() => {
this.retryDuration = Math.min(this.retryDuration * 1.5, 60000)
this.open()
}, this.retryDuration)
m.redraw()
}
onmessage(event) {
try {
let data = JSON.parse(event.data)
this.emit(data.type, data.payload)
} catch (err) { console.error(err) }
}
}
const client = new WSClient()
let encodingStatus = {}
client.on('status', function(newStatus) {
encodingStatus = newStatus
m.redraw()
})
class Header {
view(vnode) {
return [
m('h1', 'Fíladelfíu streymi'),
m('div.status.green', { hidden: true }, 'No errors'),
m('div.filler'),
client.connected
? null
: m('div.overlay', [
m('h2', client.connecting ? 'Connecting to server...' : 'Failed to connect, retrying in...'),
client.connecting
? this.viewConnectingBar()
: this.viewWaitingCircle(),
]),
m('span', 'Live:'),
m('div.led', {
class: encodingStatus.encoding ? 'led-green' : '',
}),
]
}
viewConnectingBar() {
let diff = Math.round((new Date() - client.connectingAt) / 1000)
let out = []
for (let i = 0; i < 5; i++) {
out.push(m('div.led', { class: diff >= i ? 'led-blue' : '' }))
}
return m('div.bar', out)
}
viewWaitingCircle() {
var diff = Math.min((new Date() - client.reconnectingAt + 200), client.retryDuration)
var percentage = Math.abs(Math.min(diff / client.retryDuration, 0))
return m('svg', {
onclick: function() { client.open() },
style: 'width: 150px; transform: rotate(-90deg);',
viewBox: '0 0 150 150',
preserveAspectRatio: 'xMidYMid meet',
}, [
m('filter', { id: 'darker' }, [
m('feFlood', { 'flood-color': '#000000', 'flood-opacity': '1', in: 'SourceGraphic' }),
m('feComposite', { operator: 'in', in2: 'SourceGraphic' }),
m('feGaussianBlur', { stdDeviation: '2' }), // approx size, with higher values meaning less intensity
m('feComponentTransfer', { result: 'glowd' }, m('feFuncA', { type: 'linear', slope: 1, intercept: 0 })),
m('feMerge', [
m('feMergeNode', { in: 'SourceGraphic' }),
m('feMergeNode', { in: 'glowd' }),
]),
]),
m('filter', { id: 'glower' }, [
m('feFlood', { 'flood-color': '#0066ff', 'flood-opacity': '1', in: 'SourceGraphic' }),
m('feComposite', { operator: 'in', in2: 'SourceGraphic' }),
m('feGaussianBlur', { stdDeviation: '10' }), // approx size, with higher values meaning less intensity
m('feComponentTransfer', { result: 'glow1' }, m('feFuncA', { type: 'linear', slope: 1, intercept: 0 })),
m('feFlood', { 'flood-color': '#0066ff', 'flood-opacity': '0.5', in: 'SourceGraphic' }),
m('feComposite', { operator: 'in', in2: 'SourceGraphic' }),
m('feGaussianBlur', { stdDeviation: '1' }), // approx size, with higher values meaning less intensity
m('feComponentTransfer', { result: 'glow2' }, m('feFuncA', { type: 'linear', slope: 2, intercept: 0 })),
m('feMerge', [
m('feMergeNode', { in: 'SourceGraphic' }),
m('feMergeNode', { in: 'glow2' }),
m('feMergeNode', { in: 'glow1' }),
]),
]),
m('g', { width: '150px', height: '150px', filter: 'url(#darker)',}, [
m('rect', { width: '100%', height: '100%', fill: 'transparent' }),
m('circle', {
cx: '50%',
cy: '50%',
fill: 'transparent',
stroke: '#000000',
style: `stroke-width: 12%; transform-origin: 50% 50% 0px; transition: stroke-dashoffset 100ms;`,
r: 45,
}),
]),
m('g', { width: '150px', height: '150px', filter: 'url(#glower)',}, [
m('rect', { width: '100%', height: '100%', fill: 'transparent' }),
m('circle', {
cx: '50%',
cy: '50%',
fill: 'transparent',
stroke: '#000000',
style: `stroke-dashoffset: ${Math.round(280 * percentage)}px; stroke-dasharray: 280px; stroke-width: 10%; transform-origin: 50% 50% 0px; transition: stroke-dashoffset 100ms;`,
r: 45,
})
]),
])
}
}
class Nav {
view(vnode) {
let path = m.route.get()
return [
m(m.route.Link, {
class: path === '/' || path === '' ? 'active' : '',
href: '/',
}, [
m('svg', {
viewBox: '0 0 512 512'
}, m('path', {
d: 'M32 400C32 426.5 53.49 448 80 448H496C504.8 448 512 455.2 512 464C512 472.8 504.8 480 496 480H80C35.82 480 0 444.2 0 400V48C0 39.16 7.164 32 16 32C24.84 32 32 39.16 32 48V400zM331.3 299.3C325.1 305.6 314.9 305.6 308.7 299.3L223.1 214.6L123.3 315.3C117.1 321.6 106.9 321.6 100.7 315.3C94.44 309.1 94.44 298.9 100.7 292.7L212.7 180.7C218.9 174.4 229.1 174.4 235.3 180.7L320 265.4L452.7 132.7C458.9 126.4 469.1 126.4 475.3 132.7C481.6 138.9 481.6 149.1 475.3 155.3L331.3 299.3z'
})),
m('span', 'Status')
]),
m(m.route.Link, {
class: path === '/settings' ? 'active' : '',
href: '/settings',
}, [
m('svg', {
viewBox: '0 0 512 512'
}, m('path', {
d: 'M0 416C0 407.2 7.164 400 16 400H81.6C89.01 363.5 121.3 336 160 336C198.7 336 230.1 363.5 238.4 400H496C504.8 400 512 407.2 512 416C512 424.8 504.8 432 496 432H238.4C230.1 468.5 198.7 496 160 496C121.3 496 89.01 468.5 81.6 432H16C7.164 432 0 424.8 0 416V416zM208 416C208 389.5 186.5 368 160 368C133.5 368 112 389.5 112 416C112 442.5 133.5 464 160 464C186.5 464 208 442.5 208 416zM352 176C390.7 176 422.1 203.5 430.4 240H496C504.8 240 512 247.2 512 256C512 264.8 504.8 272 496 272H430.4C422.1 308.5 390.7 336 352 336C313.3 336 281 308.5 273.6 272H16C7.164 272 0 264.8 0 256C0 247.2 7.164 240 16 240H273.6C281 203.5 313.3 176 352 176zM400 256C400 229.5 378.5 208 352 208C325.5 208 304 229.5 304 256C304 282.5 325.5 304 352 304C378.5 304 400 282.5 400 256zM496 80C504.8 80 512 87.16 512 96C512 104.8 504.8 112 496 112H270.4C262.1 148.5 230.7 176 192 176C153.3 176 121 148.5 113.6 112H16C7.164 112 0 104.8 0 96C0 87.16 7.164 80 16 80H113.6C121 43.48 153.3 16 192 16C230.7 16 262.1 43.48 270.4 80H496zM144 96C144 122.5 165.5 144 192 144C218.5 144 240 122.5 240 96C240 69.49 218.5 48 192 48C165.5 48 144 69.49 144 96z'
})),
m('span', 'Settings')
]),
]
}
}
class Status {
view(vnode) {
return [
m('div.text', 'Some text here'),
m('span', 'Hello world'),
]
}
}
class Settings {
view(vnode) {
return [
m('span', 'Settings')
]
}
}
const rootHeader = document.getElementById('header')
const rootNav = document.getElementById('nav')
const rootMain = document.getElementById('main')
const allRoutes = {
'/': Status,
'/settings': Settings,
}
m.mount(rootHeader, Header)
m.mount(rootNav, Nav)
m.route(rootMain, '/', allRoutes)