-#! .venv/bin/python3
+#!.venv/bin/python3
from flask import Flask, Response, request, render_template_string, redirect, send_from_directory, url_for
from flask_httpauth import HTTPBasicAuth
--- /dev/null
+flask
+flask_httpauth
+werkzeug
--- /dev/null
+<html>
+ <head>
+ <style>
+div {
+ display: inline-block;
+ width: 100px;
+ height: 100px;
+}
+#one {
+ background-color: var(--background);
+ color: white;
+}
+#two {
+ background-color: var(--dark);
+ color: white;
+}
+#three {
+ background-color: var(--primary);
+ color: black;
+}
+#four {
+ background-color: var(--off-primary);
+ color: black;
+}
+#five {
+ background-color: var(--secondary);
+ color: black;
+}
+#six {
+ background-color: var(--tertiary);
+ color: white;
+}
+ </style>
+ <link rel="stylesheet" href="/static/style.css">
+ </head>
+ <body>
+ <div id="one">Background</div>
+ <div id="two">Dark</div>
+ <div id="three">Primary</div>
+ <div id="four">Off-primary</div>
+ <div id="five">Secondary</div>
+ <div id="six">Tertiary</div>
+ </body>
+</html>
version="1.1"
xml:space="preserve"
id="SVGRoot"
+ sodipodi:docname="pause.svg"
+ inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
- xmlns:svg="http://www.w3.org/2000/svg"><defs
- id="defs9" />
+ xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="true"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="0.64135202"
+ inkscape:cx="413.96923"
+ inkscape:cy="477.11708"
+ inkscape:window-width="1366"
+ inkscape:window-height="740"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="SVGRoot"
+ inkscape:document-units="px"
+ showguides="true"><sodipodi:guide
+ position="499.98375,534.4491"
+ orientation="1,0"
+ id="guide1"
+ inkscape:locked="false" /><sodipodi:guide
+ position="493.69464,500.01625"
+ orientation="0,-1"
+ id="guide2"
+ inkscape:locked="false" /></sodipodi:namedview><defs
+ id="defs9"><linearGradient
+ id="linearGradient3"
+ inkscape:collect="always"><stop
+ style="stop-color:#70b783;stop-opacity:1;"
+ offset="0.25"
+ id="stop3" /><stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4" /></linearGradient><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3"
+ id="radialGradient4"
+ cx="500"
+ cy="500"
+ fx="500"
+ fy="500"
+ r="500"
+ gradientUnits="userSpaceOnUse" /></defs>
<style
type="text/css"
}
</style>
-<path
+<rect
+ style="vector-effect:non-scaling-stroke;fill:url(#radialGradient4);fill-opacity:1;stroke:none;stroke-opacity:1;-inkscape-stroke:hairline"
+ id="rect3"
+ width="1000"
+ height="1000"
+ x="0"
+ y="0" /><path
id="rect10"
- style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-dasharray:none;stroke-opacity:1"
- d="M 300,250 H 450 V 750 H 300 Z m 250,0 H 700 V 750 H 550 Z" /><path
- style="display:none;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-dasharray:none;stroke-opacity:1"
- id="path10"
- d="M 800,500 574.99999,629.90381 350,759.80762 l 0,-259.80763 0,-259.80761 225.00001,129.90381 z"
- transform="translate(-50)" /></svg>
+ style="fill:#121212;fill-opacity:1;stroke:none;stroke-width:3;stroke-dasharray:none;stroke-opacity:1"
+ d="M 360,325 H 465 V 675 H 360 Z m 175,0 H 640 V 675 H 535 Z" /></svg>
version="1.1"
xml:space="preserve"
id="SVGRoot"
+ sodipodi:docname="play.svg"
+ inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
- xmlns:svg="http://www.w3.org/2000/svg"><defs
- id="defs9" />
+ xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="0.6958339"
+ inkscape:cx="518.80197"
+ inkscape:cy="444.0715"
+ inkscape:window-width="1366"
+ inkscape:window-height="740"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="SVGRoot"
+ showguides="true"><sodipodi:guide
+ position="499.98971,554.31523"
+ orientation="1,0"
+ id="guide2"
+ inkscape:locked="false" /><sodipodi:guide
+ position="478.22102,500.16673"
+ orientation="0,-1"
+ id="guide3"
+ inkscape:locked="false" /></sodipodi:namedview><defs
+ id="defs9"><radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3"
+ id="radialGradient4"
+ cx="500"
+ cy="500"
+ fx="500"
+ fy="500"
+ r="500"
+ gradientUnits="userSpaceOnUse" /><linearGradient
+ id="linearGradient3"
+ inkscape:collect="always"><stop
+ style="stop-color:#70b783;stop-opacity:1;"
+ offset="0.25"
+ id="stop3" /><stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4" /></linearGradient></defs>
<style
type="text/css"
}
</style>
-<path
- id="rect10"
- style="display:none;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-dasharray:none;stroke-opacity:1"
- d="M 300,250 H 450 V 750 H 300 Z m 250,0 H 700 V 750 H 550 Z" /><path
- style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-dasharray:none;stroke-opacity:1"
+<rect
+ style="vector-effect:non-scaling-stroke;fill:url(#radialGradient4);fill-opacity:1;stroke:none;stroke-opacity:1;-inkscape-stroke:hairline"
+ id="rect3"
+ width="1000"
+ height="1000"
+ x="0"
+ y="0" /><path
+ style="display:inline;fill:#121212;fill-opacity:1;stroke:none;stroke-width:3;stroke-dasharray:none;stroke-opacity:1"
id="path10"
- d="M 800,500 574.99999,629.90381 350,759.80762 l 0,-259.80763 0,-259.80761 225.00001,129.90381 z"
- transform="translate(-50)" /></svg>
+ d="M 682.5,500 524.99999,590.93267 367.5,681.86533 V 499.99999 318.13467 l 157.50001,90.93266 z" /></svg>
<html>
<head>
<style>
-
body {
position: absolute;
top: 0;
right: 0;
margin: 0;
overflow: hidden;
- color: var(--background)
+ border: 1px solid var(--primary);
+ color: var(--background);
}
img {
- height: 100vh;
+ height: calc(100vh - 2px);
}
audio {
margin: 4px;
width: calc(100% - 8px);
height: 20px;
}
+
#playerMain {
display: inline-block;
vertical-align: top;
- width: calc((100vw - 100vh) * 0.5);
- height: 100vh;
-
- background-color: var(--primary);
+ width: calc(100vw - 120vh - 1px);
+ height: calc(100vh - 2px);
+}
+#canvas {
+ position: absolute;
+ top: 0;
+ left: calc(100vh - 2px);
+ height: calc(100vh - 2px);
+ width: calc(100vw - 120vh + 1px);
+ z-index: -1;
+ opacity: 0;
+ transition: opacity 1s;
+}
+.showingCanvas {
+ opacity: 1 !important;
}
#playerInfo {
+ position: absolute;
+ top: 0;
+ left: calc(100vw - 20vh);
+ width: calc(100vw - 100vh);
+ height: calc(100vh - 2px);
+ background: var(--background);
+ transition: left 0.7s cubic-bezier(.46,.03,.52,.96);
+}
+.showingInfo {
+ left: calc(100vh - 2px) !important;
+}
+#infoButton {
+ display: inline-block;
+ vertical-align: top;
+ height: 100%;
+ width: 20vh;
+ background-color: var(--off-primary);
+ border: none;
+}
+#playerInfoBox {
display: inline-block;
vertical-align: top;
- width: calc(((100vw - 100vh) * 0.5) - 9px);
- height: 100vh;
overflow: scroll;
- padding: 4px;
-
- background-color: var(--secondary);
+ height: 100%;
+ width: calc(100vw - 120vh - 12px);
+ margin: 5px;
+ color: var(--primary);
}
-#stationName {
+#playerInfoText {
+ margin-top: 0;
+ opacity: 0;
+ transition: opacity 0.5s;
+}
+.showingInfo > #playerInfoBox > #playerInfoText {
+ opacity: 1 !important;
+}
+#overlayBar {
position: absolute;
- bottom: 0;
- left: 100vh;
- margin: 0;
- color: var(--off-primary);
- font-size: 2em;
- line-height: 1em;
+ top: -1px;
+ right: -1px;
+ width: 1px;
+ height: 100vh;
+ background-color: var(--primary);
}
+
#playerPlaying {
width: calc(100% - 16px);
margin: 4px;
padding: 4px;
+
color: var(--primary);
- background-color: var(--dark);
+ background-color: var(--transparent-dark);
}
+
#pauseIcon, #playIcon {
position: absolute;
top: 0;
left: 0;
- width: 100vh;
- height: 100vh;
+ width: calc(100vh - 2px);
+ height: calc(100vh - 2px);
}
#allIcon {
opacity: 0;
opacity: 1;
}
-@media only screen and (max-width : 700px) {
- #playerMain {
- width: calc((100vw - 100vh) - 1px);
- }
- #playerInfo {
- display: none;
- }
-}
-
/* Remove default apperance */
input[type="range"] {
margin: 4px;
/* overflow: hidden; remove this line*/
background: var(--background);
}
-
/* Thumb: webkit */
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
border: none;
transition: .2s ease-in-out;
}
-
/* Thumb: Firefox */
input[type="range"]::-moz-range-thumb {
height: 10px;
<link rel="stylesheet" href="/static/style.css">
<script>
+window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
+
const player = new Audio("https://stream.ozva.co.uk:8443/critters");
+player.crossOrigin = "anonymous";
+
+// setup variables
+var canvas = "";
+var ctx = ""
+var analyser = "";
+
var playing = false;
+function getAudio() {
+ let audioContext = new AudioContext();
+ let audioSource = audioContext.createMediaElementSource(player);
+
+ // setup analyser
+ analyser = audioContext.createAnalyser();
+ audioSource.connect(analyser);
+ analyser.connect(audioContext.destination);
+ analyser.fftSize = 512;
+
+ canvas = document.getElementById('canvas');
+ dimentions = document.getElementById("canvas").getBoundingClientRect();
+ canvas.width = dimentions.width;
+ canvas.height = dimentions.height;
+ canvas.classList.add("showingCanvas");
+
+ ctx = canvas.getContext('2d');
+}
+
+function renderFrame() {
+ let points = 128;
+
+ var array = new Uint8Array(analyser.frequencyBinCount);
+ analyser.getByteFrequencyData(array);
+
+ var dataStep = Math.round((array.length / 2) / points); //sample limited data from the total array
+ var pixelStep = Math.round(canvas.width / points); //sample limited data from the total array
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ ctx.beginPath();
+ ctx.moveTo(0, canvas.height - (array[0] * (canvas.height / 255) * 0.75));
+
+ for (var i = 0; i < points; i++) {
+ var value = array[i * dataStep];
+ ctx.lineTo(pixelStep * i, canvas.height - (value * (canvas.height / 255) * 0.75) + 2);
+ }
+
+ ctx.strokeStyle = "rgb( 43 85 54 )";
+ ctx.lineWidth = 2;
+ ctx.stroke();
+
+ requestAnimationFrame(renderFrame);
+ }
+
function getInfo () {
fetch("/api/0")
.then(data => data.text())
.then(data => JSON.parse(data))
.then(data => {
document.getElementById("playerCover").src = data.cover_path;
- document.getElementById("playerInfo").innerHTML = data.info;
+ document.getElementById("playerInfoText").innerHTML = data.info;
document.getElementById("playerPlaying").innerHTML = `
- <em>Now playing:</em> <a href="${data.link}">${data.title}</a> - ${data.artist} <em>(${data.year})</em>
+ <em>Now playing:</em> <a href="${data.link}" target="_blank">${data.title}</a> - ${data.artist} <em>(${data.year})</em>
`;
})
}
document.getElementById("playIcon").style = "";
document.getElementById("pauseIcon").style = "display: none;";
} else {
+ if (!ctx) {getAudio();}
+ renderFrame();
player.play();
playing = true;
document.getElementById("playIcon").style = "display: none;";
}
}
+function toggleInfo() {
+ document.getElementById("playerInfo").classList.toggle("showingInfo");
+ console.log("done");
+}
+
setInterval(setVolume, 100);
setInterval(getInfo, 4000);
</script>
</head>
<body onload="getInfo();document.getElementById('volumeControl').style = '';">
<img id="playerCover" src="/api/cover/0" /><div id="playerMain">
+ <canvas id="canvas" class=""></canvas>
<input id="volumeControl" style="display: none;" type="range" min="0" max="1" step="0.01"/>
<noscript>
- <audio src="https://stream.ozva.co.uk:8443/critters" controls></audio><br>
+ <audio id="audio" src="https://stream.ozva.co.uk:8443/critters" controls></audio><br>
</noscript>
<p id="playerPlaying">Currently playing track info available with the JS player</p>
- </div><div id="playerInfo">
+ </div><div id="playerInfo" class="">
+ <input type="button" value="ⓘ" id="infoButton" onclick="toggleInfo();" /><div id="playerInfoBox"><p id="playerInfoText"></p></div>
</div>
- <h1 id="stationName"><em>Tree Critters Radio</em></h1>
<div id="allIcon">
<img onclick="toggleAudio();" id="playIcon" style="" src="/static/play.svg" />
<img onclick="toggleAudio();" id="pauseIcon" style="display: none;" src="/static/pause.svg" />
</div>
+ <div id="overlayBar"></div>
</body>
</html>
:root {
--background: #111111;
--dark: #151515;
+ --transparent-dark: rgba(21, 21, 21, 0.75);
--primary: #70b783;
--off-primary: #4d9961;
--secondary: #c6ce73;
<iframe style="border: none; width: 600px; height: 100px;" src="/static/player.html"></iframe><br>
<h2>Player widget with track information:</h2>
<iframe style="border: none; width: 800px; height: 100px;" src="/static/player.html"></iframe><br>
+ <iframe style="border: none; width: 800px; height: 100px;" src="/static/pallet.html"></iframe><br>
<h2>Notes:</h2>
<p>
Plently to do on the delivery front!
<li>Need a better font</li>
<li>Need a better design for the player
<ul>
- <li>Redesign of the play pause buttons</li>
- <li>Better color pallet / color choices (+ integration of lightmode?)</li>
- <li>Better display of additional information? (+ possibly a togglable overlay?)</li>
+ <li>Redesign of the play pause buttons again?</li>
+ <li>Integration of lightmode?)</li>
</ul>
</li>
<li>Integration with different types of audio for the station