diff --git a/.woodpecker.yml b/.woodpecker.yml index 496c740..3d90701 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..144ae9d --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..2e617e6 --- /dev/null +++ b/Pipfile @@ -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" diff --git a/app.py b/app.py new file mode 100644 index 0000000..5f8e4c6 --- /dev/null +++ b/app.py @@ -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') diff --git a/index.html b/index.html deleted file mode 100644 index 6007f35..0000000 --- a/index.html +++ /dev/null @@ -1,208 +0,0 @@ - - - - - live.hackerbots.net - - - - - - - - - - - - - - - - - - -
HIT IT, JACK ▶
-

- - diff --git a/static/main.css b/static/main.css new file mode 100644 index 0000000..1909e67 --- /dev/null +++ b/static/main.css @@ -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); + } +} diff --git a/static/main.js b/static/main.js new file mode 100644 index 0000000..dfe6eb0 --- /dev/null +++ b/static/main.js @@ -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"); + }); +} diff --git a/static/raccoon.gif b/static/raccoon.gif new file mode 100644 index 0000000..c926754 Binary files /dev/null and b/static/raccoon.gif differ diff --git a/static/viz.js b/static/viz.js new file mode 100644 index 0000000..ad3c52b --- /dev/null +++ b/static/viz.js @@ -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(); + } + } +} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..8382a7e --- /dev/null +++ b/templates/index.html @@ -0,0 +1,31 @@ + + + + + live.hackerbots.net + + + + + + + + + + + + + + + +

Buffering...

+
HIT IT, JACK ▶
+
Loading...
+ +