diff --git a/app.py b/app.py index 9a727f9..c710d80 100644 --- a/app.py +++ b/app.py @@ -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: diff --git a/src/dashboard.js b/src/dashboard.js index 449ccdc..b4bdccf 100644 --- a/src/dashboard.js +++ b/src/dashboard.js @@ -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} diff --git a/src/player.js b/src/player.js index 9e5000d..04be302 100644 --- a/src/player.js +++ b/src/player.js @@ -1,6 +1,8 @@ import css from './css/player.css' import Hls from 'hls.js' +css.ref() + let player let vid let btn diff --git a/templates/dashboard.html b/templates/dashboard.html index 32fbf9c..6e8087c 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -32,7 +32,7 @@ -
+

Dashboard

@@ -40,6 +40,15 @@

Information

+

+
+ +
+
+ No +
+
+
@@ -67,6 +76,52 @@

+ +

Metrics

+ + + + + + + + + + + + + + + +
TimeBytesVideoAudio
   + + + + + + + + + + + + + +
WidthHeightFrame RateCodec
    
+
+ + + + + + + + + + + +
ChannelsSample RateCodec
   
+