This commit is contained in:
Ondrej Zara 2019-03-19 22:56:39 +01:00
parent 860ad33a70
commit 17b5040766
13 changed files with 195 additions and 11 deletions

7
app/Makefile Normal file
View file

@ -0,0 +1,7 @@
LESS := $(shell npm bin)/lessc
APP := app.css
all: $(APP)
$(APP): css/*
$(LESS) css/app.less > $@

29
app/app.css Normal file
View file

@ -0,0 +1,29 @@
body {
margin: 0;
display: flex;
flex-direction: column;
height: 100vh;
}
header nav ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: row;
}
header nav ul li {
text-align: center;
flex-grow: 1;
line-height: 40px;
}
header nav ul li:hover {
background-color: red;
}
main {
flex-grow: 1;
overflow-x: hidden;
overflow-y: auto;
}
footer {
flex-basis: 40px;
}

10
app/css/app.less Normal file
View file

@ -0,0 +1,10 @@
body {
margin: 0;
display: flex;
flex-direction: column;
height: 100vh;
}
@import "header.less";
@import "main.less";
@import "footer.less";

3
app/css/footer.less Normal file
View file

@ -0,0 +1,3 @@
footer {
flex-basis: 40px;
}

17
app/css/header.less Normal file
View file

@ -0,0 +1,17 @@
header {
nav ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: row;
li {
text-align: center;
flex-grow: 1;
line-height: 40px;
&:hover { background-color:red;}
}
}
}

5
app/css/main.less Normal file
View file

@ -0,0 +1,5 @@
main {
flex-grow: 1;
overflow-x: hidden;
overflow-y: auto;
}

View file

@ -1,4 +1,48 @@
<!doctype html> <!doctype html>
<html> <html>
<script type="module" src="js/app.js"></script> <head>
</html> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Control Your Player</title>
<link rel="stylesheet" href="app.css" />
</head>
<body>
<header>
<nav>
<ul>
<li>Q</li>
<li>Playlists</li>
<li>Library</li>
<li>Misc</li>
</ul>
</nav>
</header>
<main>
main<br/>
main<br/>
main<br/>
main<br/>
main<br/>
main<br/>
main<br/>
main<br/>
main<br/>
main<br/>
main<br/>
</main>
<footer>
<span class="art"></span>
<span class="title"></span>
<span class="artist"></span>
<span class="album"></span>
<span class="time1"></span>/<span class="time2"></span>
<button class="status"></button>
<button class="stop"></button>
<button class="prev"></button>
<button class="next"></button>
<button class="repeat"></button>
<button class="random"></button>
</footer>
<script type="module" src="js/app.js"></script>
</body>
</html>

View file

@ -1,10 +1,16 @@
import * as mpd from "./mpd.js"; import * as mpd from "./mpd.js";
import * as status from "./status.js"; import * as status from "./status.js";
import * as art from "./art.js";
async function init() { async function init() {
await mpd.init(); await mpd.init();
status.init(); status.init();
window.mpd = mpd; window.mpd = mpd;
art.get("NAS/ABBA/Greatest Hits/01 Dancing Queen.mp3").then(src => {
let image = document.createElement("img");
image.src = src;
document.querySelector("main").appendChild(image);
});
} }
init(); init();

51
app/js/art.js Normal file
View file

@ -0,0 +1,51 @@
import * as mpd from "./mpd.js";
import * as parser from "./parser.js";
let cache = {};
const SIZE = 64;
async function getImageData(songUrl) {
let data = [];
let offset = 0;
while (1) {
let params = ["albumart", `"${mpd.escape(songUrl)}"`, offset];
let lines = await mpd.command(params.join(" "));
if (lines.length == 1) { return null; }
data = data.concat(lines[2]);
let metadata = parser.linesToStruct(lines.slice(0, 2));
if (data.length >= Number(metadata["size"])) { return data; }
offset += Number(metadata["binary"]);
}
}
async function bytesToImage(bytes) {
let blob = new Blob([bytes]);
let image = document.createElement("img");
image.src = URL.createObjectURL(blob);
return new Promise(resolve => {
image.onload = () => resolve(image);
});
}
async function resize(image) {
let canvas = document.createElement("canvas");
canvas.width = SIZE;
canvas.height = SIZE;
let ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, SIZE, SIZE);
return new Promise(resolve => canvas.toBlob(resolve));
}
export async function get(songUrl) {
if (songUrl in cache) { return cache[songUrl]; }
let data = await getImageData(songUrl);
let bytes = new Uint8Array(data);
let image = await bytesToImage(bytes);
let blob = await resize(image);
let url = URL.createObjectURL(blob);
cache[songUrl] = url;
return url;
}

View file

@ -24,27 +24,33 @@ function onClose(e) {
function processQueue() { function processQueue() {
if (pendingResolve || commandQueue.length == 0) { return; } if (pendingResolve || commandQueue.length == 0) { return; }
let cmd = commandQueue.shift(); let {cmd, resolve} = commandQueue.shift();
pendingResolve = resolve;
if (cmd instanceof Array) { cmd = ["command_list_begin", ...cmd, "command_list_end"].join("\n"); } if (cmd instanceof Array) { cmd = ["command_list_begin", ...cmd, "command_list_end"].join("\n"); }
ws.send(cmd); ws.send(cmd);
} }
export async function command(cmd) { export function escape(str) {
commandQueue.push(cmd); return str.replace(/(['"\\])/g, "\\$1");
processQueue(); }
return new Promise(resolve => pendingResolve = resolve); export async function command(cmd) {
return new Promise(resolve => {
commandQueue.push({cmd, resolve});
processQueue();
});
} }
export async function getStatus() { export async function getStatus() {
let lines = await command(["status", "currentsong"]); let lines = await command(["status", "currentsong"]);
lines.pop(); // "OK"
return parser.linesToStruct(lines); return parser.linesToStruct(lines);
} }
export async function init() { export async function init() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
ws = new WebSocket("ws://localhost:8080?server=0:6600"); ws = new WebSocket("ws://localhost:8080?server=raspberrypi.local");
} catch (e) { reject(e); } } catch (e) { reject(e); }
pendingResolve = resolve; pendingResolve = resolve;

View file

@ -1,5 +1,4 @@
export function linesToStruct(lines) { export function linesToStruct(lines) {
lines.pop(); // "OK"
let result = {}; let result = {};
lines.forEach(line => { lines.forEach(line => {
let cindex = line.indexOf(":"); let cindex = line.indexOf(":");

View file

@ -1,13 +1,18 @@
import * as mpd from "./mpd.js"; import * as mpd from "./mpd.js";
const DELAY = 2000; const DELAY = 2000;
const DOM = {};
async function tick() { async function tick() {
let data = await mpd.getStatus(); let data = await mpd.getStatus();
console.log(data); DOM.title.textContent = data["Title"];
// console.log(data);
setTimeout(tick, DELAY); setTimeout(tick, DELAY);
} }
export function init() { export function init() {
let node = document.querySelector("footer");
DOM.title = node.querySelector(".title");
tick(); tick();
} }

View file

@ -7,7 +7,9 @@
"node-static": "^0.7.11", "node-static": "^0.7.11",
"ws2mpd": "^1.0.0" "ws2mpd": "^1.0.0"
}, },
"devDependencies": {}, "devDependencies": {
"less": "^3.9.0"
},
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },