diff --git a/sse.go b/sse.go index 299463d..97fa230 100644 --- a/sse.go +++ b/sse.go @@ -100,6 +100,7 @@ func serveSSE(c echo.Context) error { return err } w.Flush() + return nil } defer mpdConn.Close() @@ -125,6 +126,7 @@ func serveSSE(c echo.Context) error { if err != nil { c.Logger().Error(err) } + //c.Logger().Print("status " + string(jsonStatus)) // Only send new event if different from last time if !bytes.Equal(jsonStatus, lastJsonStatus) { statusEvent := Event{ @@ -145,10 +147,11 @@ func serveSSE(c echo.Context) error { if err != nil { c.Logger().Error(err) } + //c.Logger().Print("current_song " + string(jsonCurrentSong)) // Only send new event if different from last time if !bytes.Equal(jsonCurrentSong, lastJsonCurrentSong) { currentSongEvent := Event{ - Event: []byte("status"), + Event: []byte("currentsong"), Data: []byte(string(jsonCurrentSong)), } if err := currentSongEvent.MarshalTo(w); err != nil { @@ -165,10 +168,11 @@ func serveSSE(c echo.Context) error { if err != nil { c.Logger().Error(err) } + //c.Logger().Print("queue " + string(jsonQueue)) // Only send new event if different from last time if !bytes.Equal(jsonQueue, lastJsonQueue) { queueEvent := Event{ - Event: []byte("status"), + Event: []byte("queue"), Data: []byte(string(jsonQueue)), } if err := queueEvent.MarshalTo(w); err != nil { diff --git a/static/index.html b/static/index.html index 728bce6..531509d 100644 --- a/static/index.html +++ b/static/index.html @@ -24,7 +24,7 @@
- +
@@ -61,7 +61,7 @@
-
+
@@ -226,5 +226,6 @@ + diff --git a/static/index.js b/static/index.js index a34653a..5bebb7a 100644 --- a/static/index.js +++ b/static/index.js @@ -9,7 +9,6 @@ const dialog_save_playlist = document.getElementById("save-playlist"); const control_playlist_name = document.getElementById("control-playlist-name"); const dialog_save_playlist_submit = document.querySelector("#save-playlist form button"); const dialog_save_playlist_close = document.querySelector("#save-playlist .close"); - const connection_state = document.getElementById("connection-state"); const control_update_db = document.getElementById("control-update-db"); const control_previous = document.getElementById("control-previous"); @@ -137,6 +136,7 @@ tab_search.addEventListener("click", () => { }); tab_playlists.addEventListener("click", () => { + refreshPlaylists(); if (!tab_playlists.classList.contains("active")) { tab_browser.classList.remove("active"); tab_search.classList.remove("active") @@ -215,48 +215,16 @@ control_delete_playlist.addEventListener("click", () => { }); }); -tab_browser.addEventListener("click", () => { - if (!tab_browser.classList.contains("active")) { - tab_browser.classList.add("active"); - tab_search.classList.remove("active") - tab_playlists.classList.remove("active") - document.getElementById("file-browser").style.display = "block"; - document.getElementById("search").style.display = "none"; - document.getElementById("playlist-browser").style.display = "none"; - } -}); - -tab_search.addEventListener("click", () => { - if (!tab_search.classList.contains("active")) { - tab_browser.classList.remove("active"); - tab_search.classList.add("active") - tab_playlists.classList.remove("active") - document.getElementById("file-browser").style.display = "none"; - document.getElementById("search").style.display = "block"; - document.getElementById("playlist-browser").style.display = "none"; - } -}); - -tab_playlists.addEventListener("click", () => { - refreshPlaylists(); - if (!tab_playlists.classList.contains("active")) { - tab_browser.classList.remove("active"); - tab_search.classList.remove("active") - tab_playlists.classList.add("active") - document.getElementById("file-browser").style.display = "none"; - document.getElementById("search").style.display = "none"; - document.getElementById("playlist-browser").style.display = "block"; - } -}); - // Add API calls to controls -control_update_db.addEventListener("click", () => { - console.log("Issuing database update") +control_update_db.addEventListener("click", (event) => { + console.log("Issuing database update"); fetch(`${API_URL}/update_db`).then(async r => { if (r.status === 200) { + // const idText = await r.text(); console.log(await r.text()); - event.target.disabled = true; + // event.target.dataset.jobid = idText.split(" ").pop(); + // event.target.disabled = true; } else { console.error(`API returned ${r.status}: ${r.statusText}`); } @@ -391,205 +359,3 @@ control_volume.addEventListener("change", event => { } }); }); - -// Server-Sent-Events - -if (typeof (EventSource) !== "undefined") { - const sse = new EventSource("/sse"); - sse.addEventListener("status", (event) => { - const status = JSON.parse(event.data); - console.log("test: " + event.data); - - connection_state.innerHTML = "❌ Disconnected"; // ✅ Check Mark Button - }); - - sse.onmessage = function (event) { - console.log("sse message: " + event.data); - }; -} else { - console.log("Sorry, your browser does not support server-sent events..."); -} - -// Websocket logic - -/* - -// Create WebSocket connection. -const socket = new WebSocket(`${document.location.protocol === "https:" ? "wss" : "ws"}://${document.location.host}/ws`); - -// Connection opened -socket.addEventListener("open", () => { - socket.send("Hello Server!"); -}); - -// Listen for messages and update UI state -socket.addEventListener("message", event => { - // Print out mpd response - console.log(`DEBUG: ${event.data}`); // DEBUG - - const msg = JSON.parse(event.data); - - if ("status" in msg) { - if (msg.mpd_status == null) { - - } else { - // print error if present - if ("error" in msg.mpd_status) { - console.error(msg.mpd_status.error); - } - - // update "Update DB" button - if ("updating_db" in msg.mpd_status) { - control_update_db.disabled = true; - } else { - if (control_update_db.disabled) { - console.log("Database update done.") - } - control_update_db.disabled = false; - } - - // update play/pause button - if ("state" in msg.mpd_status && msg.mpd_status.state !== "play") { // TODO: only update DOM if necessary - control_play_pause.innerHTML = "⏵︎"; // Play - } else { - control_play_pause.innerHTML = "⏸︎"; // Pause - } - - // update playback time - if ("time" in msg.mpd_status) { - const [elapsed, duration] = msg.mpd_status.time.split(":", 2) - control_progress.value = elapsed; - control_progress.max = duration; - // triggers the update of control_time element - const e = new Event("input"); - control_progress.dispatchEvent(e); - } - - // update repeat state - if ("repeat" in msg.mpd_status) { - if (msg.mpd_status.repeat === "1") { - control_repeat.innerHTML = "🔴 repeat"; // 🔴 Red Circle - control_repeat.dataset.state = "on"; - } else { - control_repeat.innerHTML = "🔘 repeat"; // 🔘 Radio Button - control_repeat.dataset.state = "off"; - } - } - - // update shuffle state - if ("random" in msg.mpd_status) { - if (msg.mpd_status.random === "1") { - control_shuffle.innerHTML = "🔴 shuffle"; // 🔴 Red Circle - control_shuffle.dataset.state = "on"; - } else { - control_shuffle.innerHTML = "🔘 shuffle"; // 🔘 Radio Button - control_shuffle.dataset.state = "off"; - } - } - - // update crossfade state - if ("xfade" in msg.mpd_status) { - control_xfade.value = msg.mpd_status.xfade; - } - - // update volume - if ("volume" in msg.mpd_status) { - control_volume.value = msg.mpd_status.volume; - } - } - } - - // update song info - if ("current_song" in msg && msg.mpd_current_song != null) { - let track; - if ("Artist" in msg.mpd_current_song && "Title" in msg.mpd_current_song) { - track = `${msg.mpd_current_song.Artist} - ${msg.mpd_current_song.Title}` - } else { - track = msg.mpd_current_song.file; - } - if (control_track.innerHTML !== `${track}`) { - control_track.innerHTML = `${track}`; - } - } - - // update queue - if ("queue" in msg && msg.mpd_queue != null) { - const tbody = document.createElement("tbody"); - msg.mpd_queue.forEach(song => { - const tr = document.createElement("tr"); - tr.dataset.song_id = song.Id; - if ("songid" in msg.mpd_status && msg.mpd_status.songid === song.Id) { - tr.classList.add("playing"); - } - const pos = document.createElement("td"); - pos.innerText = song.Pos; - const artist = document.createElement("td"); - if ("Artist" in song) { - artist.innerText = song.Artist; - } - const track = document.createElement("td"); - if ("Title" in song) { - track.innerText = song.Title; - } else { - track.innerText = song.file; - } - const album = document.createElement("td"); - // TODO: Do songs have album info attached to them? - album.innerText = ""; - const length = document.createElement("td"); - length.innerText = secondsToTrackTime(song.duration); - const actions = document.createElement("td"); - const moveUp = document.createElement("button"); - moveUp.classList.add("borderless"); - if (parseInt(song.Pos) !== 0) { - moveUp.innerHTML = "🔺"; // 🔺 Red Triangle Pointed Down - moveUp.addEventListener("click", event => { moveTrackInQueue(event, -1) }); - } else { - moveUp.innerHTML = " "; - } - const moveDown = document.createElement("button"); - moveDown.classList.add("borderless"); - if (parseInt(song.Pos) !== msg.mpd_queue.length - 1) { - moveDown.innerHTML = "🔻"; // 🔻 Red Triangle Pointed Up - moveDown.addEventListener("click", event => {moveTrackInQueue(event, 1)}); - } else { - moveDown.innerHTML = " "; - } - const remove = document.createElement("button"); - remove.classList.add("borderless"); - remove.innerHTML = "❌"; // ❌ Cross mark - remove.addEventListener("click", removeTrackFromQueue); - actions.appendChild(moveUp); - actions.appendChild(moveDown); - actions.appendChild(remove); - tr.appendChild(pos); - tr.appendChild(artist); - tr.appendChild(track); - tr.appendChild(album); - tr.appendChild(length); - tr.appendChild(actions); - tbody.appendChild(tr); - }); - const currentQueue = document.querySelector("#queue tbody") - if (currentQueue.innerHTML !== tbody.innerHTML) { - console.log("Updating queue") - currentQueue.outerHTML = tbody.outerHTML; - } - } - - if ("error" in msg) { - console.error(`MPD Error: ${msg.mpd_error}`) - } -}); - -// Request MPD status every second -window.setInterval(() => { - if (socket.readyState === socket.OPEN) { - socket.send("#status"); - connection_state.innerHTML = "✅ Connected"; // ✅ Check Mark Button - } else { - connection_state.innerHTML = "❌ Disconnected"; // ❌ Cross Mark - } -}, 1000); - -*/ diff --git a/static/sse.js b/static/sse.js new file mode 100644 index 0000000..1c63e52 --- /dev/null +++ b/static/sse.js @@ -0,0 +1,178 @@ +// Server-Sent-Events + +if (typeof (EventSource) !== "undefined") { + const sse = new EventSource("/sse"); + sse.addEventListener("status", handleStatus); + sse.addEventListener("currentsong", handleCurrentSong); + sse.addEventListener("queue", handleQueue); + sse.onmessage = (event) => { + console.log("sse message: " + event.data); + }; + sse.onerror = (err) => { + console.error("EventSource failed:", err); + connection_state.innerHTML = "❌ Disconnected"; // ❌ Cross Mark + }; + sse.onopen = () => { + console.log("EventSource connected"); + connection_state.innerHTML = "✅ Connected"; // ✅ Check Mark Button + }; +} else { + console.error("Sorry, your browser does not support server-sent events..."); +} + +function handleStatus(event) { + const status = JSON.parse(event.data); + + // print error if present + if ("error" in status) { + console.error(status.error); + } + + // update "Update DB" button + if ("updating_db" in status) { + control_update_db.disabled = true; + } else { + if (control_update_db.disabled) { + console.log("Database update done.") + } + control_update_db.disabled = false; + } + + // update play/pause button + // TODO: only update DOM if necessary + if ("state" in status && status.state !== "play") { + control_play_pause.innerHTML = "⏵︎"; // Play + } else { + control_play_pause.innerHTML = "⏸︎"; // Pause + } + + if ("songid" in status) { + control_track.dataset.songid = status.songid; + } + + // update playback time + if ("time" in status) { + const [elapsed, duration] = status.time.split(":", 2) + control_progress.value = elapsed; + control_progress.max = duration; + // triggers the update of control_time element + const e = new Event("input"); + control_progress.dispatchEvent(e); + } + + // update repeat state + if ("repeat" in status) { + if (status.repeat === "1") { + control_repeat.innerHTML = "🔴 repeat"; // 🔴 Red Circle + control_repeat.dataset.state = "on"; + } else { + control_repeat.innerHTML = "🔘 repeat"; // 🔘 Radio Button + control_repeat.dataset.state = "off"; + } + } + + // update shuffle state + if ("random" in status) { + if (status.random === "1") { + control_shuffle.innerHTML = "🔴 shuffle"; // 🔴 Red Circle + control_shuffle.dataset.state = "on"; + } else { + control_shuffle.innerHTML = "🔘 shuffle"; // 🔘 Radio Button + control_shuffle.dataset.state = "off"; + } + } + + // update crossfade state + if ("xfade" in status) { + control_xfade.value = status.xfade; + } + + // update volume + if ("volume" in status) { + control_volume.value = status.volume; + } +} + +function handleCurrentSong(event) { + const current_song = JSON.parse(event.data); + + let track; + if ("Artist" in current_song && "Title" in current_song) { + track = `${current_song.Artist} - ${current_song.Title}` + } else { + track = current_song.file; + } + // Only replace if necessary to not interrupt the animation + if (control_track.innerHTML !== `${track}`) { + control_track.innerHTML = `${track}`; + } +} + +function handleQueue(event) { + const queue = JSON.parse(event.data); + + console.log(queue); + + const tbody = document.createElement("tbody"); + queue.forEach(song => { + const tr = document.createElement("tr"); + tr.dataset.song_id = song.Id; + if (control_track.dataset.songid === song.Id) { + tr.classList.add("playing"); + } + const pos = document.createElement("td"); + pos.innerText = song.Pos; + const artist = document.createElement("td"); + if ("Artist" in song) { + artist.innerText = song.Artist; + } + const track = document.createElement("td"); + if ("Title" in song) { + track.innerText = song.Title; + } else { + track.innerText = song.file; + } + const album = document.createElement("td"); + // TODO: Do songs have album info attached to them? + album.innerText = ""; + const length = document.createElement("td"); + length.innerText = secondsToTrackTime(song.duration); + const actions = document.createElement("td"); + const moveUp = document.createElement("button"); + moveUp.classList.add("borderless"); + if (parseInt(song.Pos) !== 0) { + moveUp.innerHTML = "🔺"; // 🔺 Red Triangle Pointed Down + moveUp.addEventListener("click", event => { moveTrackInQueue(event, -1) }); + } else { + moveUp.innerHTML = " "; + } + const moveDown = document.createElement("button"); + moveDown.classList.add("borderless"); + if (parseInt(song.Pos) !== queue.length - 1) { + moveDown.innerHTML = "🔻"; // 🔻 Red Triangle Pointed Up + moveDown.addEventListener("click", event => {moveTrackInQueue(event, 1)}); + } else { + moveDown.innerHTML = " "; + } + const remove = document.createElement("button"); + remove.classList.add("borderless"); + remove.innerHTML = "❌"; // ❌ Cross mark + remove.addEventListener("click", removeTrackFromQueue); + actions.appendChild(moveUp); + actions.appendChild(moveDown); + actions.appendChild(remove); + tr.appendChild(pos); + tr.appendChild(artist); + tr.appendChild(track); + tr.appendChild(album); + tr.appendChild(length); + tr.appendChild(actions); + tbody.appendChild(tr); + }); + const currentQueue = document.querySelector("#queue tbody") + // only update queue if necessary to not interrupt user interaction + if (currentQueue.innerHTML !== tbody.innerHTML) { + console.log("Updating queue") + currentQueue.outerHTML = tbody.outerHTML; + } +}