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 #!/usr/bin/env python3
from flask import Flask, request, make_response, session, redirect, render_template, send_from_directory, jsonify 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 uuid import uuid4
from bs4 import BeautifulSoup
import requests import requests
import json import json
@ -73,9 +74,13 @@ oauth_secret = config['OAuth2']['ClientSecret']
# Database # Database
conn = sqlite3.connect(config['Streaming']['Database'], check_same_thread=False) conn = sqlite3.connect(config['Streaming']['Database'], check_same_thread=False)
# Streamer Cache # Streamer memcache
stream_cache = {} stream_cache = {}
# Stat memcache
stat_cache = False
stat_cache_updated = 0
# Check if user is a streamer # Check if user is a streamer
def valid_streamer(uuid): def valid_streamer(uuid):
streamer = None streamer = None
@ -94,6 +99,60 @@ def valid_streamer(uuid):
return streamer 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("/") @app.route("/")
def index(): def index():
streamer = False streamer = False
@ -115,6 +174,21 @@ def dashboard():
return render_template("dashboard.html", stream = streamkey, return render_template("dashboard.html", stream = streamkey,
server = config['Streaming']['PublishAddress'].format(streamer = "", host = config['Streaming']['ServerHost'])) 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") @app.route("/dashboard/data")
def dashboard_data(): def dashboard_data():
if not 'uuid' in session: if not 'uuid' in session:

View File

@ -1,5 +1,27 @@
import $ from 'jquery' 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 () { function dashboard () {
$.get("/dashboard/data", function (res) { $.get("/dashboard/data", function (res) {
if (res.error) { if (res.error) {
@ -7,14 +29,23 @@ function dashboard () {
return return
} }
var fullURL = window.location.origin + "/player/" + res.name var fullURL = window.location.origin + "/watch/" + res.name
$('#myStream').attr('src', fullURL) $('#myStream').attr('src', fullURL)
$('#stream_url').text(fullURL).attr("href", fullURL) $('#stream_url').text(fullURL).attr("href", fullURL)
console.log(res)
$('#stream_live').text(res.live ? 'Yes' : 'No')
}) })
$('#show_key').click(function () { $('#show_key').click(function () {
$('#show_key').html(window.STREAM_KEY) $('#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} export default {start: dashboard}

View File

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

View File

@ -32,7 +32,7 @@
</div> </div>
</nav> </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"> <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> <h1 class="h2">Dashboard</h1>
</div> </div>
@ -40,6 +40,15 @@
<iframe allowfullscreen src="" id="myStream" width="1542" height="651" style="display: block; width: 1542px; height: 651px;"></iframe> <iframe allowfullscreen src="" id="myStream" width="1542" height="651" style="display: block; width: 1542px; height: 651px;"></iframe>
<h1 class="h2">Information</h1> <h1 class="h2">Information</h1>
<p class="lead"> <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="row">
<div class="col-2"> <div class="col-2">
<label>Stream Server</label> <label>Stream Server</label>
@ -67,6 +76,52 @@
</div> </div>
</div> </div>
</p> </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> </main>
</div> </div>
</div> </div>