This commit is contained in:
Evert Prants 2018-08-07 00:15:23 +03:00
parent 29f111e98f
commit 176b15ee49
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
4 changed files with 166 additions and 4 deletions

78
app.py
View File

@ -1,7 +1,8 @@
#!/usr/bin/env python3
from flask import Flask, request, make_response, session, redirect, render_template, send_from_directory, jsonify
from time import gmtime, strftime
from time import gmtime, strftime, time
from uuid import uuid4
from bs4 import BeautifulSoup
import requests
import json
@ -73,9 +74,13 @@ oauth_secret = config['OAuth2']['ClientSecret']
# Database
conn = sqlite3.connect(config['Streaming']['Database'], check_same_thread=False)
# Streamer Cache
# Streamer memcache
stream_cache = {}
# Stat memcache
stat_cache = False
stat_cache_updated = 0
# Check if user is a streamer
def valid_streamer(uuid):
streamer = None
@ -94,6 +99,60 @@ def valid_streamer(uuid):
return streamer
def pull_stream_metrics(uuid):
global stat_cache
global stat_cache_updated
updated = False
if not stat_cache or stat_cache_updated < int(time()) - 5:
metric_path = stream_server + "stat"
r = requests.get(metric_path)
stat_cache = BeautifulSoup(r.text, "xml")
updated = True
print("Updating per request..")
allapps = stat_cache.find_all("application")
relevant = None
for app in allapps:
if str(app.find("name").string) == "live":
relevant = app
if not relevant:
return None
streams = relevant.find_all("stream")
if not streams:
return None
if updated:
stat_cache_updated = int(time())
relevant = None
for stream in streams:
name = stream.find("name").string
if name == uuid:
relevant = stream
if not relevant:
return None
data = {
'time' : relevant.time.string,
'bytes' : relevant.bytes_in.string,
'video': {
'width': relevant.meta.video.width.string,
'height': relevant.meta.video.height.string,
'frame_rate': relevant.meta.video.frame_rate.string,
'codec': relevant.meta.video.codec.string,
},
'audio': {
'sample_rate': relevant.meta.audio.sample_rate.string,
'channels': relevant.meta.audio.channels.string,
'codec': relevant.meta.audio.codec.string,
}
}
return data
@app.route("/")
def index():
streamer = False
@ -115,6 +174,21 @@ def dashboard():
return render_template("dashboard.html", stream = streamkey,
server = config['Streaming']['PublishAddress'].format(streamer = "", host = config['Streaming']['ServerHost']))
@app.route("/dashboard/stats")
def dashboard_stats():
if not 'uuid' in session:
return jsonify({'error': 'Unauthorized'})
streamkey = valid_streamer(session['uuid'])
if not streamkey:
return jsonify({'error': 'Unauthorized'})
data = pull_stream_metrics(streamkey)
if not data:
return jsonify({'error': 'No data was returned..'})
return jsonify(data)
@app.route("/dashboard/data")
def dashboard_data():
if not 'uuid' in session:

View File

@ -1,5 +1,27 @@
import $ from 'jquery'
// https://stackoverflow.com/a/18650828
function formatBytes(a,b){if(0==a)return"0 Bytes";var c=1024,d=b||2,e=["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"],f=Math.floor(Math.log(a)/Math.log(c));return parseFloat((a/Math.pow(c,f)).toFixed(d))+" "+e[f]}
function recursive_stats(table, subtable) {
var key
for (key in table) {
var val = table[key]
if (typeof(val) == "object") {
recursive_stats(val, key)
} else {
if (key == "time") {
var 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) {
val = formatBytes(val, 3)
}
$("#stat_" + (subtable ? subtable + "_" : "") + key).text(val)
}
}
}
function dashboard () {
$.get("/dashboard/data", function (res) {
if (res.error) {
@ -7,14 +29,23 @@ function dashboard () {
return
}
var fullURL = window.location.origin + "/player/" + res.name
var fullURL = window.location.origin + "/watch/" + res.name
$('#myStream').attr('src', fullURL)
$('#stream_url').text(fullURL).attr("href", fullURL)
console.log(res)
$('#stream_live').text(res.live ? 'Yes' : 'No')
})
$('#show_key').click(function () {
$('#show_key').html(window.STREAM_KEY)
})
setInterval(function () {
$.get("/dashboard/stats", function (res) {
if (res.error) return
recursive_stats(res, "")
})
}, 5000)
}
export default {start: dashboard}

View File

@ -1,6 +1,8 @@
import css from './css/player.css'
import Hls from 'hls.js'
css.ref()
let player
let vid
let btn

View File

@ -32,7 +32,7 @@
</div>
</nav>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4" id="page-dashboard">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1>
</div>
@ -40,6 +40,15 @@
<iframe allowfullscreen src="" id="myStream" width="1542" height="651" style="display: block; width: 1542px; height: 651px;"></iframe>
<h1 class="h2">Information</h1>
<p class="lead">
<div class="row">
<div class="col-2">
<label>Live</label>
</div>
<div class="col">
<span id="stream_live">No</span>
</div>
</div>
<div class="row">
<div class="col-2">
<label>Stream Server</label>
@ -67,6 +76,52 @@
</div>
</div>
</p>
<h1 class="h2">Metrics</h1>
<table class="table">
<tbody>
<tr>
<th>Time</th>
<th>Bytes</th>
<th>Video</th>
<th>Audio</th>
</tr>
<tr>
<td><span id="stat_time">&nbsp;</span></td>
<td><span id="stat_bytes">&nbsp;</span></td>
<td>
<table>
<tr>
<th>Width</th>
<th>Height</th>
<th>Frame Rate</th>
<th>Codec</th>
</tr>
<tr>
<td><span id="stat_video_width">&nbsp;</span></td>
<td><span id="stat_video_height">&nbsp;</span></td>
<td><span id="stat_video_frame_rate">&nbsp;</span></td>
<td><span id="stat_video_codec">&nbsp;</span></td>
</tr>
</table>
</td>
<td>
<table>
<tr>
<th>Channels</th>
<th>Sample Rate</th>
<th>Codec</th>
</tr>
<tr>
<td><span id="stat_audio_channels">&nbsp;</span></td>
<td><span id="stat_audio_sample_rate">&nbsp;</span></td>
<td><span id="stat_audio_codec">&nbsp;</span></td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</main>
</div>
</div>