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
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
95037c0f82
commit
dfca30bb66
@ -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
10
Dockerfile
Normal 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
15
Pipfile
Normal 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
51
app.py
Normal 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')
|
208
index.html
208
index.html
@ -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 ▶</div>
|
||||
<div id="metadata"><span id="title"></span><p><span id="listeners"></span></div>
|
||||
</body>
|
||||
</html>
|
116
static/main.css
Normal file
116
static/main.css
Normal 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
60
static/main.js
Normal 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
BIN
static/raccoon.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
67
static/viz.js
Normal file
67
static/viz.js
Normal 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
31
templates/index.html
Normal 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 ▶</div>
|
||||
<div id="metadata"><div id="title">Loading...</div><div id="listeners"></div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user