Maintenance commit.
This commit is contained in:
parent
dbffa4262a
commit
fb3d54205e
170
app.js
170
app.js
@ -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
5547
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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, '<').replace(/\>/g, '>')
|
||||
$('#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.')
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
}
|
||||
]),
|
||||
],
|
||||
}]
|
||||
})
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user