Skip to content

Instantly share code, notes, and snippets.

@Silica163
Last active September 25, 2025 10:04
Show Gist options
  • Select an option

  • Save Silica163/3bb8694c8dc64e514ad9cdb0e3b9f162 to your computer and use it in GitHub Desktop.

Select an option

Save Silica163/3bb8694c8dc64e514ad9cdb0e3b9f162 to your computer and use it in GitHub Desktop.
Frontend for go-librespot in **single** html file.
<!doctype html>
<html>
<head>
<title>go-librespot controller</title>
<style>
html {
width: 40em;
margin:auto;
}
section {
padding-bottom: 0.5em;
}
#song_id {
width: 30em;
}
#about_track {
display:flex;
height: 15em;
justify-content: space-between;
}
#album_art {
max-height: 100%;
}
th, td {
padding: 0 1em;
}
th {
text-align: right;
}
td {
text-align: left;
border-left: 1px solid black;
}
</style>
</head>
<body>
<section id=play_wrapper>
<input type=text id=song_id placeholder="spotify url, uri, track id">
<button id=play>Play</button>
<button id=enqueue>Enqueue</button>
</section>
<section id=about_track>
<table id=track_detail>
<tbody>
<tr>
<th scope=row>Title</th>
<td id=name_></td>
</tr>
<tr>
<th scope=row>Album</th>
<td id=album></td>
</tr>
<tr>
<th scope=row>Artist</th>
<td id=artist></td>
</tr>
<tr>
<th scope=row>Release</th>
<td id=release></th>
</tr>
</tbody>
</table>
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACXBIWXMAAAAAAAAAAQCEeRdzAAAACklEQVR4nGNgAAAAAgABSK+kcQAAAABJRU5ErkJggg=="
id=album_art>
</img>
</section>
<section id=status_line>
<p>Last status: <span id=status_last></span></p>
<p>uri: <span id=spt_uri></span></p>
</section>
<section id=control_wrapper>
<button id=resume>Play</button>
<button id=pause>Pause</button>
<button id=get_status>Get Player status</button>
</section>
<script>
document.body.onload = run;
const address = '127.0.0.1';
const port = 8080;
const backend = `http://${address}:${port}`;
function post_api(path, body = {}, callback = (res)=>{}) {
fetch(backend + path, {
mode: "cors",
method: "POST",
keepalive:false,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body)
})
.then(res => res.text())
.then(callback)
.catch(e=>{console.warn(e)});
}
function get_api(path, callback = (res)=>{console.log(res)}) {
fetch(backend + path, {
method: "GET",
mode: "cors",
cache: "reload",
keepalive: false,
})
.then(res => res.text())
.then(callback)
.catch(e=>{console.warn(e)});
}
function process_status(res){
let data = JSON.parse(res);
update_track_info(data.track);
}
function parse_date_string(date_string){
let day = date_string.match(/day\:([0-9]+)/);
let month = date_string.match(/month\:([0-9]+)/);
let year = date_string.match(/year\:([0-9]+)/);
return { day, month, year };
}
function update_track_info(track){
about_track.style.display = "flex";
name_.innerText = track.name;
artist.innerText =track.artist_names.join("\n");
album.innerText = track.album_name;
album_art.src = track.album_cover_url;
spt_uri.innerText = track.uri;
let date_ = parse_date_string(track.release_date)
let date_str = "";
if(date_.year != null) date_str += `${date_.year[1]}`;
if(date_.month != null) date_str += `-${date_.month[1].padStart(2,"0")}`;
if(date_.day != null) date_str += `-${date_.day[1].padStart(2,"0")}`;
release.innerText = date_str;
}
function construct_spotify_uri(uri_or_id_or_url){
uri_or_id_or_url = uri_or_id_or_url.trim()
// http url
if(uri_or_id_or_url.match(/^(https?:\/\/)?([a-z]+\.)?spotify.com\/track\/([0-9A-Za-z]+)$/) != null){
let id = uri_or_id_or_url.match(/^(https?:\/\/)?([a-z]+\.)?spotify.com\/track\/([0-9A-Za-z]+)$/);
id = id[id.length - 1];
return `spotify:track:${id}`;
}
// only id
if(uri_or_id_or_url.match(/^[0-9A-Za-z]+$/) != null) return `spotify:track:${uri_or_id_or_url}`;
// correct uri
if(uri_or_id_or_url.match(/^spotify:track:[0-9A-Za-z]+$/) != null) return uri_or_id_or_url;
return "";
}
function run(){
about_track.style.display = "none";
play.onclick = ()=>{
let uri = construct_spotify_uri(song_id.value);
if(uri == "") return;
post_api("/player/play", { uri });
};
enqueue.onclick = ()=>{
let uri = construct_spotify_uri(song_id.value);
if(uri == "") return;
post_api("/player/add_to_queue", { uri });
};
resume.onclick = ()=>{
post_api("/player/resume");
};
pause.onclick = ()=>{
post_api("/player/pause");
};
get_status.onclick = ()=>{
get_api("/status", process_status);
};
get_api("/status", process_status);
}
let ws;
function connect_ws(){
ws = new WebSocket(backend+"/events");
ws.onmessage = (msg_unparse) => {
let msg = JSON.parse(msg_unparse.data);
switch(msg.type){
case "metadata": {
update_track_info(msg.data);
} break;
default: {
console.log(msg);
status_last.innerText = msg.type;
}
}
}
ws.onclose = connect_ws
}
connect_ws();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment