absolutely destroy this beautiful static html file by converting it to a slightly more dynamic flask app + docker container
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Victoria Fierce 2023-03-10 11:58:21 +01:00
parent 95037c0f82
commit dfca30bb66
10 changed files with 366 additions and 208 deletions

View File

@ -1,5 +1,21 @@
pipeline:
build:
image: plugins/docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock
settings:
registry: gitea.malloc.hackerbots.net
repo: gitea.malloc.hackerbots.net/tdfischer/hackerbots-live
daemon_off: true
auto_tag: true
tags: latest
username:
from_secret: GITEA_USERNAME
password:
from_secret: GITEA_PASSWORD
deploy:
when:
branch: master
image: alpine:3.13
volumes:
- /var/web/live/:/deploy

10
Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM python:3.9-alpine
RUN pip install pipenv && pip install gunicorn==20.1.0 eventlet==0.30.2
WORKDIR /app
COPY . /app
RUN pipenv install --system
CMD ["gunicorn", "-k", "eventlet", "-w", "1", "-b", "0.0.0.0:5000", "app:app"]

15
Pipfile Normal file
View File

@ -0,0 +1,15 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
flask = "*"
flask-json = "*"
requests = "*"
flask-socketio = "*"
[requires]
python_version = "3.9"

51
app.py Normal file
View File

@ -0,0 +1,51 @@
from flask import Flask, render_template
from flask_json import FlaskJSON, as_json
import requests
from flask_socketio import SocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
json = FlaskJSON(app)
socketio = SocketIO(app, logger=True, log_output=True)
socketio.init_app(app, cors_allowed_origins="*")
CURRENT_LISTENERS = 0
def heartbeat():
global CURRENT_LISTENERS
backendFetch = requests.get('http://hackerbots.net:8000/status-json.xsl')
stats = backendFetch.json()['icestats']['source']
print(stats)
return {
'title': stats.get('title', 'Unknown Track'),
'listeners': CURRENT_LISTENERS,
'show_title': 'Flask Test Show'
}
@socketio.on('live.hello')
def on_hello(*args, **kwargs):
print("Hello!!!")
@socketio.on('connect')
def on_new_listener():
global CURRENT_LISTENERS
CURRENT_LISTENERS += 1
socketio.emit('live.heartbeat', heartbeat())
@socketio.on('disconnect')
def on_new_listener():
global CURRENT_LISTENERS
CURRENT_LISTENERS -= 1
socketio.emit('live.heartbeat', heartbeat())
@app.route("/")
def index():
return render_template('index.html')
@app.route("/status.json")
@as_json
def status():
return heartbeat()
if __name__ == '__main__':
socketio.run(app, port=5000, host='0.0.0.0')

View File

@ -1,208 +0,0 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>live.hackerbots.net</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- <script type="text/javascript" src="../dist/butterchurn.js"></script> -->
<script type="text/javascript" src="https://unpkg.com/lodash"></script>
<script type="text/javascript" src="https://unpkg.com/butterchurn"></script>
<script type="text/javascript" src="https://unpkg.com/butterchurn-presets"></script>
<script type="text/javascript" src="https://unpkg.com/butterchurn-presets/lib/butterchurnPresetsExtra.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bungee+Spice&display=swap" rel="stylesheet">
<script
src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://unpkg.com/normalize.css/normalize.css" />
<script>
$(function() {
var visualizer = null;
var rendering = false;
var sourceNode = null;
var delayedAudible = null;
var cycleInterval = null;
var presets = {};
var presetKeys = [];
var presetIndexHist = [];
var presetIndex = 0;
var presetCycle = true;
var presetCycleLength = 15000;
var presetRandom = true;
function startRenderer() {
if (!document.hidden && visualizer) {
var fps = 60;
setTimeout(() => {
requestAnimationFrame(startRenderer);
}, 1000 / fps);
visualizer.render();
}
}
function visToggle() {
if (!document.hidden) {
startRenderer();
}
}
document.addEventListener('visibilitychange', visToggle);
function initPlayer() {
var audioContext = new AudioContext();
var mediaElement = document.getElementById('audio-player');
var mediaSrc = audioContext.createMediaElementSource(mediaElement);
//var canvas = document.getElementById('canvas');
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
visualizer = butterchurn.default.createVisualizer(audioContext, canvas , {
width: canvas.width,
height: canvas.height,
pixelRatio: window.devicePixelRatio || 1,
textureRatio: 1,
});
mediaSrc.connect(audioContext.destination);
visualizer.connectAudio(mediaSrc);
var presets = {};
if (window.butterchurnPresets) {
Object.assign(presets, butterchurnPresets.getPresets());
}
if (window.butterchurnPresetsExtra) {
Object.assign(presets, butterchurnPresetsExtra.getPresets());
}
visualizer.loadPreset(_.sample(presets));
cycleInterval = setInterval(() => {
visualizer.loadPreset(_.sample(presets), 5.7);
}, presetCycleLength);
$(canvas).click(() => {
visualizer.loadPreset(_.sample(presets), 1.0);
});
}
function updateMeta() {
$.getJSON('/status.json', (data) => {
var meta = data.icestats.source;
var title = meta.title;
var listeners = meta.listeners;
console.log(data.icestats.source);
$('#title').text(title);
$('#listeners').text(listeners + " listeners");
});
}
updateMeta();
setInterval(updateMeta, 3000);
var isSetup = false;
$('#the-button').click(() => {
if (!isSetup) {
initPlayer();
isSetup = true;
$('#the-button').remove();
startRenderer();
}
});
});
</script>
<style type="text/css">
html, body, #canvas {
width: 100%;
height: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
animation: rainbow-bg 10s linear;
animation-iteration-count: infinite;
}
#the-button {
animation: throb 2.5s ease-in-out infinite alternate;
font-family: 'Bungee Spice', cursive;
font-size: 6em;
text-align: center;
}
#metadata {
position: absolute;
bottom: 0%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
color: #fff;
font-family: 'Bungee Spice', sans-serif;
text-align: center;
padding: 3rem;
}
#title {
font-size: 2rem;
}
@keyframes rainbow-bg{
100%,0%{
background-color: rgb(255,0,0);
}
8%{
background-color: rgb(255,127,0);
}
16%{
background-color: rgb(255,255,0);
}
25%{
background-color: rgb(127,255,0);
}
33%{
background-color: rgb(0,255,0);
}
41%{
background-color: rgb(0,255,127);
}
50%{
background-color: rgb(0,255,255);
}
58%{
background-color: rgb(0,127,255);
}
66%{
background-color: rgb(0,0,255);
}
75%{
background-color: rgb(127,0,255);
}
83%{
background-color: rgb(255,0,255);
}
91%{
background-color: rgb(255,0,127);
}
}
@keyframes throb{
from{
transform: scale(1.0);
}
to {
transform: scale(0.75);
}
}
</style>
</head>
<body>
<audio id='audio-player' autoplay src="https://live.hackerbots.net/listen.mp3">No audio</audio>
<div id="the-button">HIT IT, JACK &#9654;</div>
<div id="metadata"><span id="title"></span><p><span id="listeners"></span></div>
</body>
</html>

116
static/main.css Normal file
View File

@ -0,0 +1,116 @@
html, body, #canvas {
width: 100%;
height: 100%;
}
.butterviz {
width: 100%;
height: 100%;
}
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
animation: rainbow-bg 10s linear;
animation-iteration-count: infinite;
}
#loader {
font-size: xx-large;
font-family: 'Bungee Spice';
text-align: center;
position: absolute;
transition: all .75s ease;
background-color: rgba(0, 0, 0, 0.75);
visibility: hidden;
opacity: 0;
}
#loader p {
padding: 10rem 5rem 1rem 5rem;
background: url(/static/raccoon.gif);
background-repeat: no-repeat;
background-position: top center;
}
#loader.show {
visibility: visible;
opacity: 1;
}
#the-button {
animation: throb 2.5s ease-in-out infinite alternate;
font-family: 'Bungee Spice', cursive;
font-size: 6em;
text-align: center;
}
#metadata {
position: absolute;
bottom: 0%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
color: #fff;
font-family: 'Bungee Spice', sans-serif;
text-align: center;
width: 100%;
padding-top: 3rem;
display: flex;
flex-direction: column;
flex-wrap: wrap;
align-content: center;
padding-bottom: 3rem;
}
#title {
font-size: 2rem;
}
@keyframes rainbow-bg{
100%,0%{
background-color: rgb(255,0,0);
}
8%{
background-color: rgb(255,127,0);
}
16%{
background-color: rgb(255,255,0);
}
25%{
background-color: rgb(127,255,0);
}
33%{
background-color: rgb(0,255,0);
}
41%{
background-color: rgb(0,255,127);
}
50%{
background-color: rgb(0,255,255);
}
58%{
background-color: rgb(0,127,255);
}
66%{
background-color: rgb(0,0,255);
}
75%{
background-color: rgb(127,0,255);
}
83%{
background-color: rgb(255,0,255);
}
91%{
background-color: rgb(255,0,127);
}
}
@keyframes throb{
from{
transform: scale(1.0);
}
to {
transform: scale(0.75);
}
}

60
static/main.js Normal file
View File

@ -0,0 +1,60 @@
import { io } from "https://cdn.socket.io/4.4.1/socket.io.esm.min.js"
import Visualizer from "./viz.js"
export function init() {
function updateMeta(data) {
var title = data.title;
var listeners = data.listeners;
$('#title').text(title);
$('#listeners').text(listeners + " listeners");
}
function visToggle() {
if (!document.hidden) {
visualizer.start();
}
}
document.addEventListener('visibilitychange', visToggle);
var visualizer = null;
var audioPlayer = document.getElementById("audio-player");
$('#the-button').click(() => {
if (visualizer == null) {
var loaderWidget = document.getElementById("loader");
audioPlayer.play();
$(loaderWidget).addClass("show");
$('#the-button').remove();
setTimeout(() => {
visualizer = new Visualizer(audioPlayer);
setTimeout(() => {
visualizer.start();
}, 0);
}, 0);
}
});
var socket = io();
socket.on('connect', () => {
socket.emit('live.hello', {});
});
socket.on('live.heartbeat', (heartbeat) => {
console.log('heartbeat')
console.log(heartbeat)
updateMeta(heartbeat)
});
window.addEventListener("resize", _.debounce(() => {
visualizer.resize();
}, 100));
audioPlayer.addEventListener("waiting", (evt) => {
$(loaderWidget).addClass("show");
});
audioPlayer.addEventListener("playing", (evt) => {
$(loaderWidget).removeClass("show");
});
}

BIN
static/raccoon.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

67
static/viz.js Normal file
View File

@ -0,0 +1,67 @@
import "https://unpkg.com/butterchurn@2.6.7"
import "https://unpkg.com/butterchurn-presets@2.4.7"
import "https://unpkg.com/butterchurn-presets/lib/butterchurnPresetsExtra.min.js"
export default class Visualizer {
butterViz = null;
canvas = null;
cycleInterval = null;
presetCycleLength = 15000;
constructor(mediaElement) {
var audioContext = new AudioContext();
var mediaSrc = audioContext.createMediaElementSource(mediaElement);
this.canvas = document.createElement('canvas');
this.canvas.className = "butterviz";
document.body.appendChild(this.canvas);
this.canvas.width = this.canvas.offsetWidth;
this.canvas.height = this.canvas.offsetHeight;
this.butterViz = butterchurn.default.createVisualizer(audioContext, this.canvas , {
width: this.canvas.width,
height: this.canvas.height,
pixelRatio: window.devicePixelRatio || 1,
textureRatio: 1,
});
mediaSrc.connect(audioContext.destination);
this.butterViz.connectAudio(mediaSrc);
var presets = {};
if (window.butterchurnPresets) {
Object.assign(presets, butterchurnPresets.getPresets());
}
if (window.butterchurnPresetsExtra) {
Object.assign(presets, butterchurnPresetsExtra.getPresets());
}
this.cycleInterval = setInterval(() => {
this.butterViz.loadPreset(_.sample(presets), 5.7);
}, this.presetCycleLength);
$(this.canvas).click(() => {
this.butterViz.loadPreset(_.sample(presets), 1.0);
});
setTimeout(() => {
this.butterViz.loadPreset(_.sample(presets));
}, 0);
}
resize() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
this.butterViz.setRendererSize(this.canvas.offsetWidth, this.canvas.offsetHeight);
}
start() {
if (!document.hidden && this.butterViz) {
var fps = 60;
setTimeout(() => {
requestAnimationFrame(() => {this.start()});
}, 1000 / fps);
this.butterViz.render();
}
}
}

31
templates/index.html Normal file
View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>live.hackerbots.net</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="https://unpkg.com/lodash"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bungee+Spice&display=swap" rel="stylesheet">
<script
src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://unpkg.com/normalize.css/normalize.css" />
<script type="module">
import { init } from "/static/main.js"
$(init)
</script>
<link rel="stylesheet" href="/static/main.css" />
</head>
<body>
<audio crossorigin="anonymous" id='audio-player' src="https://live.hackerbots.net/listen.mp3">No audio</audio>
<div id="loader"><p>Buffering...</p></div>
<div id="the-button">HIT IT, JACK &#9654;</div>
<div id="metadata"><div id="title">Loading...</div><div id="listeners"></div>
</body>
</html>