Maintenance commit.

This commit is contained in:
Evert Prants 2020-05-28 22:06:09 +03:00
parent dbffa4262a
commit fb3d54205e
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
7 changed files with 3154 additions and 2739 deletions

170
app.js
View File

@ -5,15 +5,15 @@ const nunjucks = require('nunjucks')
const passport = require('passport')
const express = require('express')
const request = require('request')
const sqlite3 = require('sqlite3')
const sqlite = require('sqlite')
const xml2js = require('xml2js')
const WebSocket = require('ws')
const uuid = require('uuid/v4')
const redis = require('redis')
const path = require('path')
const toml = require('toml')
const http = require('http')
const URL = require('url')
const URL = require('url').URL
const fs = require('fs')
require('express-async-errors')
@ -22,7 +22,6 @@ const SessionStore = connectSession(session)
const util = require('util')
const get = util.promisify(request.get)
const post = util.promisify(request.post)
const dev = process.env.NODE_ENV === 'development'
@ -30,7 +29,7 @@ const dev = process.env.NODE_ENV === 'development'
const filename = path.join(__dirname, 'config.toml')
let config
let cache = { _updated: 0, streamers: {}, viewers: {}, live: [] }
const cache = { _updated: 0, streamers: {}, viewers: {}, live: [] }
try {
config = toml.parse(fs.readFileSync(filename))
@ -40,32 +39,33 @@ try {
}
config = Object.assign({
'Streaming': {
'port': '9322',
'database': 'streaming.db',
'streamServer': 'https://tv.icynet.eu/live/',
'serverHost': 'icynet.eu',
'publishAddress': 'rtmp://{host}:1935/hls-live/{streamer}',
'secret': 'changeme'
Streaming: {
port: '9322',
database: 'streaming.db',
streamServer: 'https://tv.icynet.eu/live/',
serverHost: 'icynet.eu',
publishAddress: 'rtmp://{host}:1935/hls-live/{streamer}',
secret: 'changeme'
},
'Auth': {
'strategy': 'passport-oauth2',
'callbackURL': 'http://localhost:5000/auth/_callback/',
'clientID': '1',
'clientSecret': 'changeme'
Auth: {
strategy: 'passport-oauth2',
callbackURL: 'http://localhost:5000/auth/_callback/',
clientID: '1',
clientSecret: 'changeme'
}
}, config)
// Constants
const port = parseInt(config['Streaming']['port'])
const streamServer = config['Streaming']['streamServer']
const streamServerHost = config['Streaming']['serverHost']
const port = parseInt(config.Streaming.port)
const streamServer = config.Streaming.streamServer
const streamServerHost = config.Streaming.serverHost
const streamAppName = streamServer.match(/\/([\w-_]+)\/$/)[1]
// Database
const dbPromise = Promise.resolve()
.then(() => sqlite.open(path.join(process.cwd(), config['Streaming']['database']), { Promise, cache: true }))
.then(db => db.migrate())
const dbPromise = sqlite.open({
filename: path.join(process.cwd(), config.Streaming.database),
driver: sqlite3.cached.Database
})
// Setup server
const app = express()
@ -73,11 +73,11 @@ const server = http.createServer(app)
const wss = new WebSocket.Server({ clientTracking: false, noServer: true })
// Authentication
const Strategy = require(config['Auth']['strategy'])
const strategyConfig = Object.assign({}, config['Auth'])
const Strategy = require(config.Auth.strategy)
const strategyConfig = Object.assign({}, config.Auth)
if (!strategyConfig.provider) strategyConfig.provider = strategyConfig.strategy.replace('passport-', '')
passport.use(new Strategy(strategyConfig, function (accessToken, refreshToken, profile, done) {
process.nextTick(function() {
process.nextTick(function () {
return done(null, profile)
})
}))
@ -104,7 +104,7 @@ nunjucks.configure('templates', {
const sessionParser = session({
key: 'Streamserver Session',
secret: config['Streaming']['secret'],
secret: config.Streaming.secret,
resave: false,
saveUninitialized: true,
store: new SessionStore({ client: redis.createClient() }),
@ -121,16 +121,16 @@ app.use(passport.session())
// Parse stream metrics from the stat.xml file
async function pullMetrics (uuid) {
let statPath = streamServer + 'stat'
const statPath = streamServer + 'stat'
if (!cache.stats || cache._updated < Date.now() - 5000) {
let { body } = await get(statPath)
let rip = await xml2js.parseStringPromise(body)
const { body } = await get(statPath)
const rip = await xml2js.parseStringPromise(body)
if (!rip.rtmp.server) throw new Error('Invalid response from server.')
// Autofind the correct server
let rtmpserver = rip.rtmp.server[0].application
const rtmpserver = rip.rtmp.server[0].application
let rtmpapp
for (let i in rtmpserver) {
for (const i in rtmpserver) {
if (rtmpserver[i].name[0] !== streamAppName) continue
rtmpapp = rtmpserver[i]
}
@ -143,7 +143,7 @@ async function pullMetrics (uuid) {
// Extract applicable stream data
let forUser
for (let i in cache.stats) {
for (const i in cache.stats) {
if (!cache.stats[i].stream) continue
if (cache.stats[i].stream[0].name[0] !== uuid) continue
forUser = cache.stats[i].stream[0]
@ -152,7 +152,7 @@ async function pullMetrics (uuid) {
if (!forUser) return null
// Generic data object
let data = {
const data = {
time: forUser.time[0],
bytes: forUser.bytes_in[0],
video: null,
@ -161,7 +161,7 @@ async function pullMetrics (uuid) {
// Add video metadata, if applicable
if (forUser.meta[0].video[0] !== '') {
data['video'] = {
data.video = {
width: forUser.meta[0].video[0].width[0],
height: forUser.meta[0].video[0].height[0],
frame_rate: forUser.meta[0].video[0].frame_rate[0],
@ -171,7 +171,7 @@ async function pullMetrics (uuid) {
// Add audio metadata, if applicable
if (forUser.meta[0].audio[0] !== '') {
data['audio'] = {
data.audio = {
sample_rate: forUser.meta[0].audio[0].sample_rate[0],
channels: forUser.meta[0].audio[0].channels[0],
codec: forUser.meta[0].audio[0].codec[0]
@ -185,16 +185,16 @@ async function pullMetrics (uuid) {
app.post('/publish', async (req, res) => {
if (!req.body.name) throw new Error('Invalid request.')
let db = await dbPromise
const db = await dbPromise
// Validate stream key
let streamer = await db.get('SELECT * FROM channels WHERE key=?', req.body.name)
const streamer = await db.get('SELECT * FROM channels WHERE key=?', req.body.name)
if (!streamer) throw new Error('Invalid stream key.')
console.log('=> Streamer %s has started streaming!', streamer.name)
// Generate real publish address for the server
let publishAddress = config['Streaming']['publishAddress']
const publishAddress = config.Streaming.publishAddress
.replace('{streamer}', streamer.name)
.replace('{host}', '127.0.0.1')
@ -211,8 +211,8 @@ app.post('/publish', async (req, res) => {
app.post('/publish_done', async (req, res) => {
if (!req.body.name) throw new Error('Invalid request.')
let db = await dbPromise
let chan = await db.get('SELECT * FROM channels WHERE key = ?', req.body.name)
const db = await dbPromise
const chan = await db.get('SELECT * FROM channels WHERE key = ?', req.body.name)
console.log('<= Streamer %s has stopped streaming!', chan.name)
try { delete cache.viewers[chan.name] } catch (e) {}
@ -230,14 +230,14 @@ app.get('/login', passport.authenticate(strategyConfig.provider, Object.assign({
app.get('/auth/_callback', passport.authenticate(strategyConfig.provider, { failureRedirect: '/' }), async (req, res) => {
dev && console.log(req.user.username, 'logged in')
// Get user from database
let db = await dbPromise
let user = await db.get('SELECT * FROM signed_users WHERE uuid=?', req.user.uuid)
const db = await dbPromise
const user = await db.get('SELECT * FROM signed_users WHERE uuid=?', req.user.uuid)
if (!user) {
await db.run('INSERT INTO signed_users (uuid,name) VALUES (?,?)', req.user.uuid, req.user.username)
}
// Lets see if this user is a streamer
let streamer = await db.get('SELECT * FROM channels WHERE user_uuid = ?', req.user.uuid)
const streamer = await db.get('SELECT * FROM channels WHERE user_uuid = ?', req.user.uuid)
if (streamer) cache.streamers[req.user.uuid] = streamer
res.redirect('/')
@ -263,8 +263,8 @@ app.use(async function (req, res, next) {
res.locals.user = req.user
if (!cache.streamers[req.user.uuid]) {
let db = await dbPromise
let streamer = await db.get('SELECT * FROM channels WHERE user_uuid = ?', req.user.uuid)
const db = await dbPromise
const streamer = await db.get('SELECT * FROM channels WHERE user_uuid = ?', req.user.uuid)
if (streamer) cache.streamers[req.user.uuid] = streamer
}
@ -283,13 +283,13 @@ app.get('/', (req, res) => {
// Dashboard
app.get('/dashboard', authed, (req, res) => {
let stream = cache.streamers[req.user.uuid]
const stream = cache.streamers[req.user.uuid]
res.render('dashboard.html', { stream: stream.key, server: 'rtmp://' + streamServerHost + '/live/' })
})
// Stats
app.get('/dashboard/stats', authed, async (req, res) => {
let stream = cache.streamers[req.user.uuid]
const stream = cache.streamers[req.user.uuid]
let data
try {
@ -304,10 +304,10 @@ app.get('/dashboard/stats', authed, async (req, res) => {
// Data
app.get('/dashboard/data', authed, async (req, res) => {
let stream = cache.streamers[req.user.uuid]
const stream = cache.streamers[req.user.uuid]
let data
let db = await dbPromise
const db = await dbPromise
try {
data = await db.get('SELECT * FROM channels WHERE key=?', stream.key)
@ -318,30 +318,30 @@ app.get('/dashboard/data', authed, async (req, res) => {
if (!data) return res.jsonp({ error: 'Unauthorized' })
res.jsonp({
'name': data.name,
'key': stream.key,
'uuid': req.user.uuid,
'live': data.live_at != null,
'live_at': new Date(parseInt(data.live_at)),
'last_stream': new Date(parseInt(data.last_stream))
name: data.name,
key: stream.key,
uuid: req.user.uuid,
live: data.live_at != null,
live_at: new Date(parseInt(data.live_at)),
last_stream: new Date(parseInt(data.last_stream))
})
})
// Get links
app.get('/dashboard/link', authed, async (req, res) => {
let user = req.user.uuid
const user = req.user.uuid
let db = await dbPromise
let links = await db.all('SELECT * FROM link WHERE uuid = ?', user)
const db = await dbPromise
const links = await db.all('SELECT * FROM link WHERE uuid = ?', user)
res.jsonp(links)
})
// Add link URL
app.post('/dashboard/link', authed, async (req, res) => {
let user = req.user.uuid
let name = req.body.name
let url = req.body.url
const user = req.user.uuid
const name = req.body.name
const url = req.body.url
if (name == null || url == null) return res.jsonp({ error: 'Missing parameters!' })
if (name.length > 120) return res.jsonp({ error: 'Only 120 characters are allowed in the name.' })
@ -350,15 +350,15 @@ app.post('/dashboard/link', authed, async (req, res) => {
url.indexOf('<') !== -1 || url.indexOf('>') !== -1) return res.jsonp({ error: 'HTML tags are forbidden!' })
// Validate URL
let a = URL.parse(url)
if (a.protocol === null || a.host === null || a.slashes !== true) return res.jsonp({ error: 'Invalid URL!' })
const a = new URL(url)
if (a.protocol === '' || a.host === '') return res.jsonp({ error: 'Invalid URL!' })
// Checks
let db = await dbPromise
let links = await db.all('SELECT * FROM link WHERE uuid = ?', user)
const db = await dbPromise
const links = await db.all('SELECT * FROM link WHERE uuid = ?', user)
if (links.length > 10) return res.jsonp({ error: 'You can currently only add up to 10 links!' })
let link = await db.get('SELECT * FROM link WHERE url = ? AND uuid = ?', url, user)
const link = await db.get('SELECT * FROM link WHERE url = ? AND uuid = ?', url, user)
if (link) return res.jsonp({ error: 'This URL already exists!' })
// Add
@ -368,12 +368,12 @@ app.post('/dashboard/link', authed, async (req, res) => {
// Remove link URL
app.post('/dashboard/link/delete', authed, async (req, res) => {
let user = req.user.uuid
const user = req.user.uuid
if (req.body.name == null && req.body.url == null) return res.jsonp({ error: 'Missing parameters!' })
// Check
let db = await dbPromise
const db = await dbPromise
let link = await db.get('SELECT * FROM link WHERE url = ? AND uuid = ?', req.body.url, user)
if (!link) {
link = await db.get('SELECT * FROM link WHERE name = ? AND uuid = ?', req.body.name, user)
@ -397,11 +397,11 @@ app.get('/player/:name', (req, res) => {
// Public data
app.get('/api/channel/:name', async (req, res) => {
let name = req.params.name
let db = await dbPromise
let data = await db.get('SELECT user_uuid,name,live_at,last_stream FROM channels WHERE name=?', name)
const name = req.params.name
const db = await dbPromise
const data = await db.get('SELECT user_uuid,name,live_at,last_stream FROM channels WHERE name=?', name)
if (!data) return res.jsonp({ error: 'No such channel!' })
let links = await db.all('SELECT name,url FROM link WHERE uuid = ?', data.user_uuid)
const links = await db.all('SELECT name,url FROM link WHERE uuid = ?', data.user_uuid)
delete data.user_uuid
data.live = data.live_at != null
@ -424,7 +424,7 @@ app.use((error, req, res, next) => {
wss.on('connection', (ws, request, client) => {
let userId = request.session.id
let username = 'A Friendly Guest'
let myChannels = []
const myChannels = []
if (request.user) {
userId = request.user.uuid
@ -434,8 +434,8 @@ wss.on('connection', (ws, request, client) => {
dev && console.log(userId, 'connected')
ws.on('message', (msg) => {
dev && console.log(userId, 'said', msg)
let is = msg.toString().trim().split(' ')
let chan = is[1]
const is = msg.toString().trim().split(' ')
const chan = is[1]
if (!chan) return
switch (is[0]) {
case 'watch':
@ -461,9 +461,9 @@ wss.on('connection', (ws, request, client) => {
ws.on('close', () => {
dev && console.log(userId, 'disconnected')
for (let i in myChannels) {
let chan = myChannels[i]
let viewers = cache.viewers[chan]
for (const i in myChannels) {
const chan = myChannels[i]
const viewers = cache.viewers[chan]
if (viewers && viewers[userId]) delete cache.viewers[chan][userId]
}
})
@ -481,7 +481,7 @@ server.on('upgrade', (request, socket, head) => {
request.user = request.session.passport.user
}
wss.handleUpgrade(request, socket, head, function(ws) {
wss.handleUpgrade(request, socket, head, function (ws) {
wss.emit('connection', ws, request)
})
})
@ -492,11 +492,15 @@ const host = dev ? '0.0.0.0' : '127.0.0.1'
server.listen(port, host, () => {
// Get currently live channels, for example, when server restarted while someone was live
(async function () {
let db = await dbPromise
let allLive = await db.all('SELECT name FROM channels WHERE live_at IS NOT NULL')
for (let i in allLive) {
const db = await dbPromise
await db.migrate()
const allLive = await db.all('SELECT name FROM channels WHERE live_at IS NOT NULL')
for (const i in allLive) {
cache.live.push(allLive[i].name)
}
console.log(`=> Found ${cache.live.length} channels still live`)
})().catch(e => console.error(e.stack))

5547
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,30 +12,31 @@
"serve": "NODE_ENV=\"development\" node app.js"
},
"devDependencies": {
"bootstrap": "^4.3.1",
"copy-webpack-plugin": "^5.0.2",
"file-loader": "^1.1.11",
"hls.js": "^0.10.1",
"jquery": "^3.4.1",
"popper.js": "^1.14.4",
"webpack": "^4.16.4",
"webpack-command": "^0.4.1"
"bootstrap": "^4.5.0",
"copy-webpack-plugin": "^6.0.1",
"file-loader": "^6.0.0",
"hls.js": "^0.13.2",
"jquery": "^3.5.1",
"popper.js": "^1.16.1",
"webpack": "^4.43.0",
"webpack-command": "^0.5.0"
},
"dependencies": {
"body-parser": "^1.18.3",
"connect-redis": "^4.0.3",
"ejs": "^2.6.1",
"express": "^4.16.4",
"body-parser": "^1.19.0",
"connect-redis": "^4.0.4",
"ejs": "^3.1.3",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"express-session": "^1.16.1",
"nunjucks": "^3.2.0",
"passport": "^0.4.0",
"redis": "^2.8.0",
"request": "^2.88.0",
"sqlite": "^3.0.3",
"express-session": "^1.17.1",
"nunjucks": "^3.2.1",
"passport": "^0.4.1",
"redis": "^3.0.2",
"request": "^2.88.2",
"sqlite": "^4.0.9",
"sqlite3": "^4.2.0",
"toml": "^3.0.0",
"uuid": "^3.3.2",
"ws": "^7.2.0",
"xml2js": "^0.4.22"
"uuid": "^8.1.0",
"ws": "^7.3.0",
"xml2js": "^0.4.23"
}
}

View File

@ -3,21 +3,21 @@ import $ from 'jquery'
// https://stackoverflow.com/a/18650828
function formatBytes (a, b) {
if (a === 0) return '0 Bytes'
let c = 1024
let d = b || 2
let e = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
let f = Math.floor(Math.log(a) / Math.log(c))
const c = 1024
const d = b || 2
const e = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const f = Math.floor(Math.log(a) / Math.log(c))
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + ' ' + e[f]
}
function recursiveStats (table, subtable) {
for (let key in table) {
for (const key in table) {
let val = table[key]
if (typeof val === 'object') {
recursiveStats(val, key)
} else {
if (key === 'time') {
let date = new Date(null)
const date = new Date(null)
date.setSeconds(Math.floor(parseInt(val) / 1000)) // specify value for SECONDS here
val = date.toISOString().substr(11, 8)
} else if (key.indexOf('bytes') !== -1) {
@ -35,17 +35,17 @@ function updateLinkList (k) {
$('#link-list').html('<tbody></tbody>')
$('#link-list tbody').append('<tr><th>Name</th><th>URL</th><th>Action</th></tr>')
for (let i in res) {
let p = res[i]
for (const i in res) {
const p = res[i]
p.name = p.name.replace(/\</g, '&lt;').replace(/\>/g, '&gt;')
$('#link-list tbody').append('<tr data-url="' + p.url + '"><td>' + p.name +'</td><td>' +
'<a href="' + p.url +'" target="_blank" rel="nofollow">' +
$('#link-list tbody').append('<tr data-url="' + p.url + '"><td>' + p.name + '</td><td>' +
'<a href="' + p.url + '" target="_blank" rel="nofollow">' +
p.url + '</a></td><td><a href="#" class="delete-link">Remove</a></td></tr>')
}
$('.delete-link').click(function (e) {
e.preventDefault()
let pr = $(this).parent().parent().attr('data-url')
const pr = $(this).parent().parent().attr('data-url')
$.post('/dashboard/link/delete', { url: pr }, updateLinkList)
})
})
@ -58,9 +58,11 @@ function dashboard (k) {
return
}
let fullURL = window.location.origin + '/watch/' + res.name
const fullURL = window.location.origin + '/watch/' + res.name
const sourceURL = window.location.origin + '/live/' + res.name + '.m3u8'
$('#myStream').attr('src', fullURL)
$('#stream_url').text(fullURL).attr('href', fullURL)
$('#source_url').text(sourceURL).attr('href', sourceURL)
$('#stream_live').text(res.live ? 'Yes' : 'No')
})
@ -70,7 +72,7 @@ function dashboard (k) {
})
$('.go-page').click(function (e) {
let el = $(this)
const el = $(this)
$('.go-page').removeClass('active')
el.addClass('active')
$('.page:visible').fadeOut(function () {
@ -80,8 +82,8 @@ function dashboard (k) {
$('#add-link').submit(function (e) {
e.preventDefault()
let name = $('input[name="name"]').val()
let url = $('input[name="url"]').val()
const name = $('input[name="name"]').val()
const url = $('input[name="url"]').val()
if (name.length > 120) return alert('Only 120 characters are allowed in the name.')

View File

@ -2,21 +2,21 @@
import Hls from 'hls.js'
// Elements
let player = document.querySelector('.livecnt')
let vid = player.querySelector('#stream')
let overlay = player.querySelector('.overlay')
let btn = overlay.querySelector('#playbtn')
let time = overlay.querySelector('#duration')
let fullscreenbtn = overlay.querySelector('#fullscrbtn')
let playbtn = overlay.querySelector('#playbtn')
let mutebtn = overlay.querySelector('#mutebtn')
let lstat = overlay.querySelector('.live')
let opts = overlay.querySelector('.controls')
let bigbtn = overlay.querySelector('.bigplaybtn')
let volumebar = overlay.querySelector('#volume_seek')
let volumeseek = volumebar.querySelector('.seeker')
let volumeseekInner = volumeseek.querySelector('.seekbar')
let viewers = overlay.querySelector('.viewers')
const player = document.querySelector('.livecnt')
const vid = player.querySelector('#stream')
const overlay = player.querySelector('.overlay')
const btn = overlay.querySelector('#playbtn')
const time = overlay.querySelector('#duration')
const fullscreenbtn = overlay.querySelector('#fullscrbtn')
const playbtn = overlay.querySelector('#playbtn')
const mutebtn = overlay.querySelector('#mutebtn')
const lstat = overlay.querySelector('.live')
const opts = overlay.querySelector('.controls')
const bigbtn = overlay.querySelector('.bigplaybtn')
const volumebar = overlay.querySelector('#volume_seek')
const volumeseek = volumebar.querySelector('.seeker')
const volumeseekInner = volumeseek.querySelector('.seekbar')
const viewers = overlay.querySelector('.viewers')
let links
let linksList
@ -39,7 +39,7 @@ function GET (url, istext) {
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
resolve(xmlHttp.responseText)
} else if (xmlHttp.readyState === 4 && xmlHttp.status >= 400) {
let err = new Error(xmlHttp.status)
const err = new Error(xmlHttp.status)
err.request = xmlHttp
reject(err)
}
@ -93,21 +93,21 @@ function handleWebSocket () {
if (!live || ws) return
ws = new WebSocket(`ws${location.protocol.indexOf('s') !== -1 ? 's' : ''}://${location.host}`)
ws.onerror = function(e) {
ws.onerror = function (e) {
console.error('Socket errored, retrying..', e)
ws.close()
ws = null
setTimeout(() => handleWebSocket(), 5000)
}
ws.onopen = function() {
ws.onopen = function () {
console.log('Upstream socket connection established')
if (!vid.paused) ws.send('watch ' + STREAM_NAME)
ws.onmessage = function (event) {
if (!event) return
let message = event.data
const message = event.data
if (message.indexOf('viewlist ') === 0) {
let str = message.substring(9)
const str = message.substring(9)
let list = str.split(',')
if (str === '') list = []
viewersCount(list)
@ -115,7 +115,7 @@ function handleWebSocket () {
}
}
ws.onclose = function() {
ws.onclose = function () {
console.error('Socket died, retrying..')
ws = null
setTimeout(() => handleWebSocket(), 5000)
@ -160,8 +160,8 @@ function resetHide () {
}
function updateTime () {
let minutes = Math.floor(vid.currentTime / 60)
let seconds = Math.floor(vid.currentTime - minutes * 60)
const minutes = Math.floor(vid.currentTime / 60)
const seconds = Math.floor(vid.currentTime - minutes * 60)
time.innerHTML = minutes + ':' + (seconds < 10 ? '0' + seconds : seconds)
}
@ -178,7 +178,7 @@ function toggleStream () {
}
function toggleSound () {
let muteicon = mutebtn.querySelector('.fa')
const muteicon = mutebtn.querySelector('.fa')
if (vid.muted) {
vid.muted = false
muteicon.className = 'fa fa-volume-up fa-fw'
@ -270,11 +270,11 @@ volumeseek.addEventListener('click', (e) => {
updateVolume()
})
let mousewheelevt = (/Firefox/i.test(navigator.userAgent)) ? 'DOMMouseScroll' : 'mousewheel'
const mousewheelevt = (/Firefox/i.test(navigator.userAgent)) ? 'DOMMouseScroll' : 'mousewheel'
volumebar.addEventListener(mousewheelevt, (e) => {
e.preventDefault()
let scrollAmnt = (e.wheelDelta == null ? e.detail * -40 : e.wheelDelta)
const scrollAmnt = (e.wheelDelta == null ? e.detail * -40 : e.wheelDelta)
if (scrollAmnt < 0) {
vid.volume = clampAddition(-0.1)
@ -299,7 +299,7 @@ if (Hls.isSupported()) {
hls.stopLoad()
clearTimeout(retryTimeout)
})
hls.on(Hls.Events.ERROR, (e,d) => {
hls.on(Hls.Events.ERROR, (e, d) => {
if (!d.fatal) return // Don't attempt to recover the stream when a non-fatal error occurs
vidReady = false
@ -318,23 +318,23 @@ if (Hls.isSupported()) {
}
// helper function to get an element's exact position
function getPosition(el) {
function getPosition (el) {
let xPosition = 0
let yPosition = 0
while (el) {
if (el.tagName == 'BODY') {
// deal with browser quirks with body/window/document and page scroll
let xScrollPos = el.scrollLeft || document.documentElement.scrollLeft
let yScrollPos = el.scrollTop || document.documentElement.scrollTop
const xScrollPos = el.scrollLeft || document.documentElement.scrollLeft
const yScrollPos = el.scrollTop || document.documentElement.scrollTop
xPosition += (el.offsetLeft - xScrollPos + el.clientLeft)
yPosition += (el.offsetTop - yScrollPos + el.clientTop)
} else {
xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft)
yPosition += (el.offsetTop - el.scrollTop + el.clientTop)
}
el = el.offsetParent
}
return {
@ -349,8 +349,8 @@ function hideOnClickOutside (element) {
}
const outsideClickListener = event => {
if ((!element.contains(event.target) && isVisible(element))
&& (event.target !== links && !links.contains(event.target))) {
if ((!element.contains(event.target) && isVisible(element)) &&
(event.target !== links && !links.contains(event.target))) {
element.style.display = 'none'
removeClickListener()
}
@ -390,7 +390,7 @@ function updateLinks (srcs) {
hideOnClickOutside(linksList)
let pos = getPosition(links)
const pos = getPosition(links)
linksList.style = 'display: block;'
pos.x -= linksList.offsetWidth - links.offsetWidth / 2
pos.y -= linksList.offsetHeight
@ -401,9 +401,9 @@ function updateLinks (srcs) {
links.style.display = 'block'
linksList.innerHTML = ''
for (let i in srcs) {
let link = srcs[i]
let el = document.createElement('a')
for (const i in srcs) {
const link = srcs[i]
const el = document.createElement('a')
el.href = link.url
el.innerText = link.name
el.target = '_blank'
@ -413,7 +413,7 @@ function updateLinks (srcs) {
function getStreamStatus () {
GET('/api/channel/' + STREAM_NAME).then((data) => {
let jd = JSON.parse(data)
const jd = JSON.parse(data)
if (jd.error) {
errored = true
return alert(jd.error)

View File

@ -78,12 +78,21 @@
<div class="row">
<div class="col-2">
<label>Stream Link</label>
<label>Stream URL</label>
</div>
<div class="col">
<a href="" id="stream_url"></a>
</div>
</div>
<div class="row">
<div class="col-2">
<label>Source URL</label>
</div>
<div class="col">
<a href="" id="source_url"></a>
</div>
</div>
</p>
<h1 class="h2">Metrics</h1>

View File

@ -14,8 +14,8 @@ module.exports = {
rules: []
},
plugins: [
new CopyPlugin([
{
new CopyPlugin({
patterns: [{
from: 'src/css/*.css',
to: 'css',
flatten: true
@ -24,7 +24,7 @@ module.exports = {
from: 'node_modules/bootstrap/dist/css/bootstrap.min.css',
to: 'css',
flatten: true
}
]),
],
}]
})
]
}