<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
const ADDRESS = "ws://127.0.0.1:32100";
class WebsocketWrapper {
ws = undefined;
data = null;
interval = undefined;
intervalId = undefined;
update;
filter;
connect() {
const ws = new WebSocket(ADDRESS);
ws.onclose = (e) => {
console.log(
"Socket is closed. Reconnect will be attempted in 5 seconds. Reason:",
e.reason
);
this.ws = undefined;
clearInterval(this.intervalId);
this.intervalId = undefined;
this.data = {};
setTimeout(this.connect, 5000);
};
ws.onmessage = (e) => {
if (!e.data) {
this.data = null;
return;
}
let data = null;
if (typeof e.data == "object") {
data = e.data;
} else {
try {
data = JSON.parse(e.data);
} catch {
data = e.data;
}
}
if (this.filter(data)) {
this.data = data;
}
};
this.ws = ws;
}
constructor(
update = (ws, data) => {},
interval = 1000,
filter = (data) => true
) {
this.update = update;
this.filter = filter;
this.interval = interval;
this.intervalId = setInterval(() => {
if (this.ws?.readyState === WebSocket.OPEN) {
this.update(this.ws, this.data);
}
}, this.interval);
this.connect();
}
}
</script>
<script>
let imageFetcher = undefined;
let imageUrl = undefined;
let currentImage = undefined;
function applyStatus(data, elements) {
if (!imageFetcher) {
imageFetcher = new WebsocketWrapper(
(ws, data) => {
ws.send("artwork/0");
if (data != currentImage) {
if (imageUrl) {
URL.revokeObjectURL(imageUrl);
imageUrl = undefined;
}
if (data) {
if (data instanceof Blob) {
imageUrl = URL.createObjectURL(data);
} else {
imageUrl = data;
}
}
currentImage = data;
}
},
350,
(data) => !!data
);
}
const titleElement = elements["title"];
const artistElement = elements["artist"];
const albumElement = elements["album"];
const statusElement = elements["status"];
const barElement = elements["bar"];
const barCircleElement = elements["barCircle"];
const positionElement = elements["position"];
const artElement = elements["art"];
let paused = data?.playbackState == "paused";
let position = data?.position;
let metadata = data?.metadata;
let title = metadata?.title;
let artist = metadata?.artist;
let album = metadata?.album;
let length = metadata?.length;
let art = metadata?.artwork?.at(0);
if (titleElement) {
if (title) {
titleElement.textContent = title;
} else {
titleElement.textContent = "Nothing playing!";
}
}
if (artistElement) {
if (artist) {
artistElement.textContent = `${artist}`;
} else {
artistElement.textContent = "";
}
}
if (albumElement) {
if (album) {
albumElement.textContent = `${album}`;
} else {
albumElement.textContent = "";
}
}
if (statusElement && barElement && barCircleElement) {
if (position && length) {
let posPercentage = (position / length) * 100.0;
statusElement.hidden = false;
barCircleElement.style.left = `${posPercentage}%`;
} else {
statusElement.hidden = true;
barCircleElement.style.left = "0.0%";
}
}
if (positionElement) {
if (position && length) {
let posSeconds = (Math.round(position / 1_000_000) % 60)
.toString()
.padStart(2, "0");
let posMinutes = Math.floor(position / 1_000_000 / 60);
let lenSeconds = (Math.round(length / 1_000_000) % 60)
.toString()
.padStart(2, "0");
let lenMinutes = Math.floor(length / 1_000_000 / 60);
var paused_text = "";
if (paused) {
paused_text = " (Paused!)";
}
positionElement.textContent =
`${posMinutes}:${posSeconds} / ${lenMinutes}:${lenSeconds}` +
paused_text;
} else {
positionElement.textContent = "";
}
}
if (artElement) {
if (art) {
artElement.style.backgroundImage = `url("${imageUrl}")`;
} else {
artElement.style.backgroundImage = "";
}
}
}
</script>
<script>
function load() {
const elements = {
title: document.getElementById("np-title"),
artist: document.getElementById("np-artist"),
album: document.getElementById("np-album"),
status: document.getElementById("np-status"),
bar: document.getElementById("np-bar"),
barCircle: document.getElementById("np-bar-circle"),
position: document.getElementById("np-position"),
art: document.getElementById("np-art"),
};
const ws = new WebsocketWrapper((ws, data) => {
ws.send(null);
applyStatus(data, elements);
}, 250);
}
window.addEventListener("load", load);
</script>
<style>
* {
box-sizing: border-box;
}
:root {
font-size: 24px;
margin: 0;
padding: 0;
}
body {
margin: 0;
padding: 0;
}
#nowplaying {
width: 100vw;
height: 100vh;
display: flex;
}
#np-info {
width: 50%;
display: flex;
flex-direction: column;
padding: 8px;
margin-right: 8px;
}
#np-text {
text-align: center;
height: 100%;
}
#np-bar {
width: 100%;
padding-top: 2px;
margin-block: 6px;
height: 10px;
position: relative;
}
#np-bar-background {
width: 100%;
height: 100%;
background-color: rgb(255, 255, 255);
border: solid 3px rgb(70, 65, 83);
border-radius: 8px;
position: absolute;
}
#np-bar-circle {
left: 0;
top: 0;
transform: translateX(-50%);
width: 8px;
height: 8px;
background-color: rgb(248, 175, 175);
border: solid 3px rgb(255, 134, 134);
border-radius: 50%;
box-sizing: content-box;
position: absolute;
}
#np-art {
width: 50%;
opacity: 0.5;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
</style>
<title>MPRIS Now-playing</title>
</head>
<body>
<div id="nowplaying">
<div id="np-info">
<div id="np-text">
<h3 id="np-title"></h3>
<i id="np-album"></i>
<p id="np-artist"></p>
</div>
<div id="np-status" hidden>
<div id="np-bar">
<div id="np-bar-background"></div>
<div id="np-bar-circle"></div>
</div>
<div id="np-position"></div>
</div>
</div>
<div id="np-art" />
</div>
</body>
</html>