Last active
September 25, 2025 10:04
-
-
Save Silica163/3bb8694c8dc64e514ad9cdb0e3b9f162 to your computer and use it in GitHub Desktop.
Frontend for go-librespot in **single** html file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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