From f17ab0c42696794b58923a8fb23e87728fcba5b2 Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:53:25 -0500 Subject: [PATCH 01/17] broken timer lololol --- Client/scripts.js | 62 +++++++++++++++++++++++++++++++++++------------ wishlist.md | 3 ++- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/Client/scripts.js b/Client/scripts.js index dbf3382..7e26d7b 100644 --- a/Client/scripts.js +++ b/Client/scripts.js @@ -5,6 +5,10 @@ let adminPass = ""; const ERR_NO_ADMIN = 401; const VALID_FILE_EXT = ["mp3","flac","wav"]; +let playlistTimeTimer=null; +let playlistElapsedSeconds=0; +let playlistSongLength=-1; + const params = new URLSearchParams(location.search); let darkmodetemp = getCookie("darkmode"); @@ -97,10 +101,12 @@ function getCookie(cname) { //someone more organised than me would have set all these html elements to variables so they dont have to get them 50 times // also someone who likes things not being dumb more than me would have separated the client and server buttons async function controlButton(buttonType) { + clearInterval(playlistTimeTimer); if (buttonType == "pp") { // Play-Pause button getFromServer({control: "play-pause"}, "controls") } else if (buttonType == "sk") { // Skip button - let returnCode = getFromServer({control: "skip"}, "controls"); + let returnCode = await getFromServer({control: "skip"}, "controls"); + console.log(returnCode["ok"]) if(returnCode["ok"]) { if (document.getElementById("playlist-mode").style.display == "block") { generateVisualPlaylist("skip-button"); @@ -245,6 +251,21 @@ function qrCodeGenerate() { }); } +async function displayElapsedPlaylistTime(elapsed=0,length=0) { + if(Math.floor(elapsed) === Math.floor(length)){ + console.log("somethingShouldBeHappening") + playlistElapsedSeconds = 0; + generateVisualPlaylist(); + } + let mins = Math.floor(elapsed/60); + let secs = Math.floor(elapsed%60); + let durMins = Math.floor(length/60); + let durSecs = Math.floor(length%60); + let timeLeft = document.getElementById("elapsed-time-display"); + timeLeft.innerHTML = mins.toString() +":"+ secs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}) + "/"+ durMins.toString()+":"+durSecs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}); + playlistElapsedSeconds++; +} + async function checkSettings(skipServer=false) { //check client stuff first so if the server doesn't exist it can still be changed and seen if (ip.slice(-5)=="19054") { @@ -299,6 +320,7 @@ async function generateVisualPlaylist(conditions="") { return { filename, ...songData }; // Merge filename with song data }); if (playlist.length==0){ + clearInterval(playlistTimeTimer); document.getElementById("playlist-alert").innerHTML = "Nothing's Queued..." } else { if (conditions=="skip-button") { @@ -330,20 +352,11 @@ async function generateVisualPlaylist(conditions="") { let head5 = document.createElement("h5"); let timeLeft =document.createElement("h5"); timeLeft.style.fontWeight = 100; - try { - if (i == 0) { // Only the first song in the loop gets a time - head5.innerHTML="Playing"; - if ((conditions != "skip-button")) { - let mins = Math.floor(playlist[i]["time"]/60); - let secs = Math.floor(playlist[i]["time"]%60); - let durMins = Math.floor(playlist[i]["length"]/60); - let durSecs = Math.floor(playlist[i]["length"]%60); - timeLeft.innerHTML = mins.toString() +":"+ secs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}) + "/"+ durMins.toString()+":"+durSecs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}); - } - } - }catch(err){ - // i dont know why there's a try catch here but i'm leaving it i dont want to break something - console.error(err) + if(i== 0) { + // they can all have the text, doesn't really matter, but only the first one + // should get the ids since its the one we want to mess with + head5.id = "playing-indicator-text"; + timeLeft.id = "elapsed-time-display"; } let textdiv = document.createElement("div") textdiv.className="text" @@ -354,8 +367,25 @@ async function generateVisualPlaylist(conditions="") { textdiv.appendChild(head5); newItem.appendChild(textdiv); document.getElementById("playlist").appendChild(newItem); + try { + if (i == 0) { // Only the first song in the loop gets a time + head5.innerHTML="Playing"; + if ((conditions != "skip-button")) { + playlistElapsedSeconds = playlist[0]["time"]; + playlistSongLength = playlist[0]["length"]; + displayElapsedPlaylistTime(playlistElapsedSeconds,playlistSongLength); + clearInterval(playlistTimeTimer); + playlistTimeTimer = setInterval(() => { + displayElapsedPlaylistTime(playlistElapsedSeconds,playlistSongLength); + },1000) + } + } + }catch(err){ + // i dont know why there's a try catch here but i'm leaving it i dont want to break something + console.error(err) + } } - } + } } async function submitSong(songid) { diff --git a/wishlist.md b/wishlist.md index 9fb170c..e1324e1 100644 --- a/wishlist.md +++ b/wishlist.md @@ -26,4 +26,5 @@ - [ ] Websockets / some method of updating the time remaining to any client on the playlist screen * currently the screen just grabs the "elapsed time" once when it is loaded * websockets can re-update clients - * not actually sure if i can CORS-socket but we're sure gonna try \ No newline at end of file + * not actually sure if i can CORS-socket but we're sure gonna try + - [ ] Set a timeout to change the time \ No newline at end of file From 384b369eeeea1bd878c3575295d1cdf3ac63638c Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:06:43 -0500 Subject: [PATCH 02/17] Update scripts.js --- Client/scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/scripts.js b/Client/scripts.js index 7e26d7b..7435742 100644 --- a/Client/scripts.js +++ b/Client/scripts.js @@ -71,7 +71,7 @@ async function getFromServer(bodyInfo, source="",password=adminPass) { } catch(e) { // console.log("error print here:"); // console.log(e); - if (e.toString().contains("TypeError: Failed to fetch")){ + if (e.toString().includes("TypeError: Failed to fetch")){ alertText("Error: Can't Connect to Server (is the ip set?)") } else { alertText("Error: " + e); From f064183b9a38d4241ffa342a9522e7881402d9d9 Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Sat, 31 Jan 2026 00:08:53 -0500 Subject: [PATCH 03/17] Sockets are implemented need a lot of fixes but are in a mostly working state as of now --- Client/index.html | 1 + Client/scripts.js | 78 +++++++++++++++++++++++++++++++++------------ Server/webbyBits.py | 50 +++++++++++++++++++++++++---- wishlist.md | 14 ++++---- 4 files changed, 111 insertions(+), 32 deletions(-) diff --git a/Client/index.html b/Client/index.html index a45c6b1..d2e4234 100644 --- a/Client/index.html +++ b/Client/index.html @@ -6,6 +6,7 @@ + diff --git a/Client/scripts.js b/Client/scripts.js index 7435742..e8b7604 100644 --- a/Client/scripts.js +++ b/Client/scripts.js @@ -8,6 +8,7 @@ const VALID_FILE_EXT = ["mp3","flac","wav"]; let playlistTimeTimer=null; let playlistElapsedSeconds=0; let playlistSongLength=-1; +let currentlyPlaying = false; const params = new URLSearchParams(location.search); @@ -101,10 +102,12 @@ function getCookie(cname) { //someone more organised than me would have set all these html elements to variables so they dont have to get them 50 times // also someone who likes things not being dumb more than me would have separated the client and server buttons async function controlButton(buttonType) { - clearInterval(playlistTimeTimer); if (buttonType == "pp") { // Play-Pause button - getFromServer({control: "play-pause"}, "controls") + let result = await getFromServer({control: "play-pause"}, "controls"); + console.log(result); + currentlyPlaying = result["data"]["playingState"]; } else if (buttonType == "sk") { // Skip button + clearInterval(playlistTimeTimer); let returnCode = await getFromServer({control: "skip"}, "controls"); console.log(returnCode["ok"]) if(returnCode["ok"]) { @@ -113,6 +116,7 @@ async function controlButton(buttonType) { } } } else if (buttonType == "pl") { // Playlist button + clearInterval(playlistTimeTimer); document.getElementById("songlist").innerHTML = ""; document.getElementById("playlist").innerHTML = "

"; document.getElementById("playlist-mode").style.display = "block"; @@ -120,12 +124,14 @@ async function controlButton(buttonType) { document.getElementById("settings-mode").style.display = "none"; generateVisualPlaylist(); } else if (buttonType == "se") { //SearchMode button + clearInterval(playlistTimeTimer); document.getElementById("songlist").innerHTML = "

Search to find songs!

"; document.getElementById("playlist").innerHTML = ""; document.getElementById("playlist-mode").style.display = "none"; document.getElementById("songlist-mode").style.display = "block"; document.getElementById("settings-mode").style.display = "none"; } else if (buttonType == "st") { //Settings button + clearInterval(playlistTimeTimer); document.getElementById("songlist").innerHTML = ""; document.getElementById("playlist").innerHTML = ""; document.getElementById("playlist-mode").style.display = "none"; @@ -251,19 +257,28 @@ function qrCodeGenerate() { }); } -async function displayElapsedPlaylistTime(elapsed=0,length=0) { - if(Math.floor(elapsed) === Math.floor(length)){ - console.log("somethingShouldBeHappening") - playlistElapsedSeconds = 0; - generateVisualPlaylist(); +async function displayElapsedPlaylistTime(elapsed=0,length=-1) { + if(currentlyPlaying) { + if(Math.floor(elapsed) > Math.floor(length) && typeof length === "number" && typeof elapsed === "number"){ + // console.log("somethingShouldBeHappening") + playlistElapsedSeconds = 0; + generateVisualPlaylist(); + } + let mins = Math.floor(elapsed/60); + let secs = Math.floor(elapsed%60); + let durMins = Math.floor(length/60); + let durSecs = Math.floor(length%60); + let timeLeft = document.getElementById("elapsed-time-display"); + if(mins > durMins) { + mins = durMins; + if(secs > durSecs) { + secs = durSecs; + } + } + + timeLeft.innerHTML = mins.toString() +":"+ secs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}) + "/"+ durMins.toString()+":"+durSecs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}); + // playlistElapsedSeconds++; } - let mins = Math.floor(elapsed/60); - let secs = Math.floor(elapsed%60); - let durMins = Math.floor(length/60); - let durSecs = Math.floor(length%60); - let timeLeft = document.getElementById("elapsed-time-display"); - timeLeft.innerHTML = mins.toString() +":"+ secs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}) + "/"+ durMins.toString()+":"+durSecs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}); - playlistElapsedSeconds++; } async function checkSettings(skipServer=false) { @@ -313,7 +328,8 @@ async function checkSettings(skipServer=false) { async function generateVisualPlaylist(conditions="") { document.getElementById("playlist").innerHTML = "

"; data = await getFromServer(null, "playlist"); - playlist = data["data"]; + playlist = data["data"]["playlist"]; + currentlyPlaying = data["data"]["playingState"] playlist = Object.values(playlist).map(obj => { const filename = Object.keys(obj)[0]; // Get the filename const songData = obj[filename]; // Get the song metadata @@ -323,7 +339,7 @@ async function generateVisualPlaylist(conditions="") { clearInterval(playlistTimeTimer); document.getElementById("playlist-alert").innerHTML = "Nothing's Queued..." } else { - if (conditions=="skip-button") { + if (conditions==="skip-button") { playlist.shift() if (playlist.length==0){ document.getElementById("playlist-alert").innerHTML = "Nothing's Queued..." @@ -375,9 +391,6 @@ async function generateVisualPlaylist(conditions="") { playlistSongLength = playlist[0]["length"]; displayElapsedPlaylistTime(playlistElapsedSeconds,playlistSongLength); clearInterval(playlistTimeTimer); - playlistTimeTimer = setInterval(() => { - displayElapsedPlaylistTime(playlistElapsedSeconds,playlistSongLength); - },1000) } } }catch(err){ @@ -386,6 +399,9 @@ async function generateVisualPlaylist(conditions="") { } } } + playlistTimeTimer = setInterval(() => { + displayElapsedPlaylistTime(playlistElapsedSeconds,playlistSongLength); + },1000) } async function submitSong(songid) { @@ -565,4 +581,26 @@ if (alertTime == "") { document.cookie = "alertTime="+alertTime+"; path=/;" } // this is the code that makes the qr code at the very start -qrCodeGenerate() \ No newline at end of file +qrCodeGenerate() + +// socket testing stuff + +socket = io("http://"+ip,{ + reconnectionAttemps: 5, + timeout: 10000, +}); + +socket.on("songAdd", function(data) { + console.log("recieved data from songAdd"); + console.log(data); + generateVisualPlaylist(); +}) + +socket.on("timeUpdate", function(data) { + console.log("recieved data from timeUpdate"); + console.log(data); + playlistElapsedSeconds = data["elapsedTime"]; + playingState = data["playingState"] +}); + +socket.on("skipSong",generateVisualPlaylist) \ No newline at end of file diff --git a/Server/webbyBits.py b/Server/webbyBits.py index ea876d8..02636da 100644 --- a/Server/webbyBits.py +++ b/Server/webbyBits.py @@ -1,6 +1,7 @@ from flask import Flask from flask import request from flask_cors import CORS +from flask_socketio import SocketIO import sqlite3 as sql import vlc,threading,time,random,argparse,dotenv,os,hashlib,string # Argparse Stuff @@ -64,20 +65,46 @@ player.audio_set_volume(100) app = Flask(__name__) # because you are POSTing from another domain to this one, you need CORS CORS(app) +# Replace the star with the frontend domain if you dislike being hacked +socketio = SocketIO(app, cors_allowed_origins="*") def queueSong(song): with playlistLock: playlist.append(song) + socketio.emit("songAdd",getSongInfo(song)) + +def getSongInfo(song): + fileofDB = sql.connect("songDatabase.db") + songDatabase = fileofDB.cursor() + songDatabase.execute("SELECT * FROM songs WHERE filename = ?",[song]) + result = songDatabase.fetchall()[0] + # again, this is still using the old JSON format to avoid client changes + k = { + "title": result[1], + "artist": result[2], + "art": result[3], + "length": result[4] + } + fileofDB.close() + return {song:k} # this is a loop that plays the songs and checks for playlist changes, skips, ect. +counter = 0 def playQueuedSongs(): global skipNow global songNext global partyMode + global counter while True: with playlistLock: + counter+=1 + if(counter > 10): + playingState = str(player.get_state()) == "State.Playing" + socketio.emit('timeUpdate',{"elapsedTime":player.get_time()/1000,"playingState":playingState}) playerState = str(player.get_state()) endStates = ["State.Ended","State.Stopped","State.NothingSpecial"] + if playerState == "State.Ended": + socketio.emit("skipSong",None) if playlist and (playerState in endStates or skipNow == True): # New song is in the queue and (the previous song is over or skip has been pressed) player.stop() @@ -87,6 +114,7 @@ def playQueuedSongs(): player.set_media(media) player.play() elif (skipNow==True or (playerState in endStates)): + # print(playerState) # skip was pressed and there are no new songs skipNow=False songNext = None @@ -99,11 +127,16 @@ def playQueuedSongs(): # adds the random songs for party mode # the above 2 means this only applies if (a song is playing or paused) and (the queue is empty) playlist.append(result[0][0]) + socketio.emit('songAdd',getSongInfo(result[0][0])) # check for new songs every second # I just didn't want to eat too much processing looping # this also has another useful affect that skips get "queued" to only 1 per second, that way somebody usually can't skip twice accidentally time.sleep(1) +@socketio.on("connect") +def handleConnect(): + pass + @app.route("/controls", methods=['POST']) def playerControls(): # recieve control inputs (play/pause and skip) from the webUI @@ -113,10 +146,12 @@ def playerControls(): try: if recieveData["control"] == "play-pause": if ADMIN_PASS == recieveData['password'] or controlPerms["PP"]: + playingState = str(player.get_state())=="State.Playing" player.pause() - return ERR_200 + return {"error":"ok","data":{"playingState":not(playingState)}},200 else: - return ERR_NO_ADMIN + playingState = str(player.get_state())=="State.Playing" + return {"error":"Admin Restricted Action","data":{"playingState":playingState}},401 elif recieveData["control"] == "skip": if ADMIN_PASS == recieveData['password'] or controlPerms["SK"]: skipNow = True @@ -163,7 +198,7 @@ def settingsControl(): elif recieveData["setting"] == "perms": if ADMIN_PASS == recieveData["password"]: controlPerms = recieveData["admin"] - print(recieveData["admin"]) + # print(recieveData["admin"]) return ERR_200 else: return ERR_NO_ADMIN @@ -261,8 +296,11 @@ def getPlaylist(): } tempPlaylist.append({i:k}) fileofDB.close() - - return {"error":"ok","data":tempPlaylist} + playingState = False + if(str(player.get_state())=="State.Playing"): + playingState = True + # print(playingState) + return {"error":"ok","data":{"playlist":tempPlaylist,"playingState":playingState}},200 if __name__ == "__main__": # There's not really a whole lot of point to a main function for something like this, you'd never use any of these methods @@ -271,5 +309,5 @@ if __name__ == "__main__": queueThread = threading.Thread(target=playQueuedSongs) queueThread.daemon = True queueThread.start() - app.run(host='0.0.0.0', port=portTheUserPicked) + socketio.run(app=app,host='0.0.0.0', port=portTheUserPicked) \ No newline at end of file diff --git a/wishlist.md b/wishlist.md index e1324e1..48f75e6 100644 --- a/wishlist.md +++ b/wishlist.md @@ -1,7 +1,5 @@ ## Wishlist *Features I would like to add, will be completed in any order* -- [x] Admin password - * Allows restricting certain features and changing permissions on the fly on the client - [ ] Refactoring existing code - [x] Remove old comments - [ ] Update the SQL -> Server -> Client pipeline when searching and building playlist @@ -24,7 +22,11 @@ - Potentially a "redemption code" system, which can be tracked client side - All of this is very hackable without a server-side login. - [ ] Websockets / some method of updating the time remaining to any client on the playlist screen - * currently the screen just grabs the "elapsed time" once when it is loaded - * websockets can re-update clients - * not actually sure if i can CORS-socket but we're sure gonna try - - [ ] Set a timeout to change the time \ No newline at end of file + * This is implemented in a very broken way right now + - [x] Set a timeout to change the time (to start) + - [x] Send updates to the playlist in real time when songs are added + * This is only kind of done, still needs work + - [ ] Update the playlist's html without destroying it (create 1 new element) + - [x] Tell clients looking at the playlist when the song has been paused (so they can pause the local timers) + * Again, still needs work + * This is currently solved by just sending the time and "playing status" once a second-ish \ No newline at end of file From 87687506b1b9c7a4461470fdeb5bb2d67da121bc Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:24:02 -0500 Subject: [PATCH 04/17] Playlist update instead of refresh, time/skip sync - Playlist destroys and creates members on the fly - Updates time live, and ensures skips aren't detected twice Im sure there are still bugs, but ill find them as i go I am also still going to refactor this, so it''s not going to be merged into main for a while --- Client/scripts.js | 105 ++++++++++++++++++++++++++++++++++++++------ Server/webbyBits.py | 12 +++-- wishlist.md | 1 + 3 files changed, 102 insertions(+), 16 deletions(-) diff --git a/Client/scripts.js b/Client/scripts.js index e8b7604..159b716 100644 --- a/Client/scripts.js +++ b/Client/scripts.js @@ -2,6 +2,7 @@ let ip; let alertTime = 2; let adminPass = ""; +let justSkipped = false const ERR_NO_ADMIN = 401; const VALID_FILE_EXT = ["mp3","flac","wav"]; @@ -104,15 +105,17 @@ function getCookie(cname) { async function controlButton(buttonType) { if (buttonType == "pp") { // Play-Pause button let result = await getFromServer({control: "play-pause"}, "controls"); - console.log(result); + // console.log(result); currentlyPlaying = result["data"]["playingState"]; } else if (buttonType == "sk") { // Skip button - clearInterval(playlistTimeTimer); + // clearInterval(playlistTimeTimer); let returnCode = await getFromServer({control: "skip"}, "controls"); - console.log(returnCode["ok"]) + // console.log(returnCode["ok"]) if(returnCode["ok"]) { if (document.getElementById("playlist-mode").style.display == "block") { - generateVisualPlaylist("skip-button"); + skipInPlaylist(); + playlistElapsedSeconds = 0; + justSkipped = true; } } } else if (buttonType == "pl") { // Playlist button @@ -277,7 +280,7 @@ async function displayElapsedPlaylistTime(elapsed=0,length=-1) { } timeLeft.innerHTML = mins.toString() +":"+ secs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}) + "/"+ durMins.toString()+":"+durSecs.toLocaleString('en-US', {minimumIntegerDigits: 2,useGrouping: false}); - // playlistElapsedSeconds++; + playlistElapsedSeconds++; } } @@ -325,6 +328,76 @@ async function checkSettings(skipServer=false) { document.getElementById("duplicateallowesettingcheckbox").checked = currentAdminPerms["DUP"]; } +async function addToPlaylist(songObject) { + i = document.getElementById("playlist").children.length-1 + let newItem = document.createElement("div"); + newItem.className = "item"; + newItem.id = Object.keys(songObject)[0]; + newItem.tabIndex = 0; + let image = document.createElement("img"); + try { + if (songObject[newItem.id]["art"] == null) { + throw "no image lolz" + } + image.src = songObject[newItem.id]["art"]; + } catch(err){ + image.src = "./images/placeholder.png"; + } + image.id = String(songObject[newItem.id])+" image"; + let head3 = document.createElement("h3"); + head3.innerText = songObject[newItem.id]["title"]; + let head4 = document.createElement("h4"); + head4.innerText= songObject[newItem.id]["artist"]; + let head5 = document.createElement("h5"); + let timeLeft =document.createElement("h5"); + timeLeft.style.fontWeight = 100; + if(i==0) { + // they can all have the text, doesn't really matter, but only the first one + // should get the ids since its the one we want to mess with + head5.id = "playing-indicator-text"; + timeLeft.id = "elapsed-time-display"; + } + let textdiv = document.createElement("div") + textdiv.className="text" + newItem.appendChild(image); + textdiv.appendChild(head3); + textdiv.appendChild(head4); + textdiv.appendChild(timeLeft); + textdiv.appendChild(head5); + newItem.appendChild(textdiv); + document.getElementById("playlist").appendChild(newItem); + try { + if (i == 0) { // Only the first song in the loop gets a time + head5.innerHTML="Playing"; + playlistElapsedSeconds = playlist[0]["time"]; + playlistSongLength = playlist[0]["length"]; + displayElapsedPlaylistTime(playlistElapsedSeconds,playlistSongLength); + clearInterval(playlistTimeTimer); + } + } catch(e) { + console.log("I dunno something bad happened:"+e); + } +} + +async function skipInPlaylist() { + playlistElapsedSeconds = 0; + let playlistChildren = document.getElementById("playlist").children; + if(playlistChildren[1].nodeName === "DIV") { + playlistChildren[1].remove(); + } + playlistChildren = document.getElementById("playlist").children; + if(playlistChildren.length === 1) { + playlistChildren[0].innerText = "Nothing's Queued..." + } else { + let firstElementTextChildren = playlistChildren[1].children[1].children + // console.log(firstElementTextChildren); + firstElementTextChildren[2].id = "elapsed-time-display"; + firstElementTextChildren[3].id = "playing-indicator-text"; + firstElementTextChildren[3].textContent = "Playing"; + } + displayElapsedPlaylistTime(playlistElapsedSeconds,playlistSongLength); +} + async function generateVisualPlaylist(conditions="") { document.getElementById("playlist").innerHTML = "

"; data = await getFromServer(null, "playlist"); @@ -398,10 +471,10 @@ async function generateVisualPlaylist(conditions="") { console.error(err) } } + playlistTimeTimer = setInterval(() => { + displayElapsedPlaylistTime(playlistElapsedSeconds,playlistSongLength); + },1000) } - playlistTimeTimer = setInterval(() => { - displayElapsedPlaylistTime(playlistElapsedSeconds,playlistSongLength); - },1000) } async function submitSong(songid) { @@ -591,16 +664,22 @@ socket = io("http://"+ip,{ }); socket.on("songAdd", function(data) { - console.log("recieved data from songAdd"); + // console.log("recieved data from songAdd"); console.log(data); - generateVisualPlaylist(); + addToPlaylist(data); }) socket.on("timeUpdate", function(data) { - console.log("recieved data from timeUpdate"); + // console.log("recieved data from timeUpdate"); console.log(data); playlistElapsedSeconds = data["elapsedTime"]; - playingState = data["playingState"] + currentlyPlaying = data["playingState"] }); -socket.on("skipSong",generateVisualPlaylist) \ No newline at end of file +socket.on("skipSong",() => { + if(justSkipped === false) { + skipInPlaylist(); + } else { + justSkipped = false; + } +}) \ No newline at end of file diff --git a/Server/webbyBits.py b/Server/webbyBits.py index 02636da..f78961c 100644 --- a/Server/webbyBits.py +++ b/Server/webbyBits.py @@ -90,21 +90,22 @@ def getSongInfo(song): # this is a loop that plays the songs and checks for playlist changes, skips, ect. counter = 0 +isPlaying = False def playQueuedSongs(): global skipNow global songNext global partyMode global counter + global isPlaying while True: with playlistLock: counter+=1 - if(counter > 10): + if(counter > 2): playingState = str(player.get_state()) == "State.Playing" socketio.emit('timeUpdate',{"elapsedTime":player.get_time()/1000,"playingState":playingState}) + counter = 0 playerState = str(player.get_state()) endStates = ["State.Ended","State.Stopped","State.NothingSpecial"] - if playerState == "State.Ended": - socketio.emit("skipSong",None) if playlist and (playerState in endStates or skipNow == True): # New song is in the queue and (the previous song is over or skip has been pressed) player.stop() @@ -113,7 +114,12 @@ def playQueuedSongs(): media = vlcInstance.media_new(soundLocation+songNext) player.set_media(media) player.play() + isPlaying = True + socketio.emit("skipSong",None) elif (skipNow==True or (playerState in endStates)): + if(isPlaying): + socketio.emit("skipSong",None) + isPlaying = False # print(playerState) # skip was pressed and there are no new songs skipNow=False diff --git a/wishlist.md b/wishlist.md index 48f75e6..8be1c9d 100644 --- a/wishlist.md +++ b/wishlist.md @@ -1,5 +1,6 @@ ## Wishlist *Features I would like to add, will be completed in any order* +- [ ] Loading indicator while awaiting server stuff - [ ] Refactoring existing code - [x] Remove old comments - [ ] Update the SQL -> Server -> Client pipeline when searching and building playlist From 4c24f13c09761e26e2f7cccace73b2d53fea5bdb Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:19:13 -0500 Subject: [PATCH 05/17] Update readme, slight gramatical change --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 4c82c73..b730a50 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ The client is a web application that can be hosted on any server, it need not be ### Server Setup: **Pre-setup:** If you want the songs to have art associated with them, it is all hosted on and retrieved from LastFM, and you will need to sign up for a developer app, and put your key in the database generator \ \ -The server side consists of 3 files: +The server side consists of 3 files and a directory: ``` sound/ From 17632d4dea1dc30bdd9013fb4e5abb1400cafc22 Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:20:05 -0500 Subject: [PATCH 06/17] New settings socket, Different controls css --- Client/index.html | 2 ++ Client/scripts.js | 61 +++++++++++++++++++++++++++++++-------------- Client/styles.css | 1 + Server/webbyBits.py | 3 +++ 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/Client/index.html b/Client/index.html index d2e4234..591a402 100644 --- a/Client/index.html +++ b/Client/index.html @@ -6,6 +6,8 @@ + + diff --git a/Client/scripts.js b/Client/scripts.js index 159b716..7d68143 100644 --- a/Client/scripts.js +++ b/Client/scripts.js @@ -2,7 +2,8 @@ let ip; let alertTime = 2; let adminPass = ""; -let justSkipped = false +let justSkipped = false; +let justChangedSetting = false; const ERR_NO_ADMIN = 401; const VALID_FILE_EXT = ["mp3","flac","wav"]; @@ -41,12 +42,16 @@ async function alertText(text="Song Added!") { } // a lot of this is kinda waffly because i was trying to get // it to return the right stuff and javascript is asyrcronouse (boo) -async function getFromServer(bodyInfo, source="",password=adminPass) { +async function getFromServer(bodyInfo, source="", secure=false, password=adminPass) { try{ if (bodyInfo != null) { // the currently set password is always included in every request bodyInfo["password"] = password; } + let href = ""; + if(secure) { + href = "https://"+ip+"/" + } const response = await fetch("http://"+ip+"/"+source, { method: "POST", body: JSON.stringify(bodyInfo), @@ -142,8 +147,13 @@ async function controlButton(buttonType) { document.getElementById("settings-mode").style.display = "block"; checkSettings() } else if (buttonType = "pm") { //Partymode toggle (in settings) - await getFromServer({setting: "partymode-toggle"}, "settings") - checkSettings(true) + let response = await getFromServer({setting: "partymode-toggle"}, "settings") + if(response.ok) { + justChangedSetting = true; + checkSettings(); + } else { + // dont think anything is needed here + } } @@ -517,19 +527,19 @@ function toggleDark(e) { qrCodeGenerate(); } -async function sha256(message) { - // Encode the message as UTF-8 - const msgBuffer = new TextEncoder().encode(message); +// async function sha256(message) { +// // Encode the message as UTF-8 +// const msgBuffer = new TextEncoder().encode(message); - // Hash the message - const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); +// // Hash the message +// const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); - // Convert ArrayBuffer to hex string - const hashArray = Array.from(new Uint8Array(hashBuffer)); - const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); +// // Convert ArrayBuffer to hex string +// const hashArray = Array.from(new Uint8Array(hashBuffer)); +// const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); - return hashHex; -} +// return hashHex; +// } async function adminPassEnter(e) { if (e.key == "Enter") { @@ -559,6 +569,8 @@ async function submitPerms(e) { // its not perfect if you spam click, but it gets the point across to the user let clickedBox = e.srcElement; clickedBox.checked = !clickedBox.checked; + } else { + justChangedSetting = true; } } @@ -627,8 +639,9 @@ document.getElementById("songlist").addEventListener('keydown', function(e){chec document.getElementById("songlist").addEventListener('click', function(e){checkWhatSongWasClicked(e)}); //makes the controls look mostly normal on all screens, best solution i could find, idk man -let tempWidth = document.getElementById('controls').clientWidth; -document.getElementById("controls").style.marginLeft = "-"+String(parseInt(tempWidth/2))+"px"; +// replaced this with "transform" css stuff +// let tempWidth = document.getElementById('controls').clientWidth; +// document.getElementById("controls").style.marginLeft = "-"+String(parseInt(tempWidth/2))+"px"; //for my use case (my immediate family), they dont know how to set an ip //using this allows the creator of the link for, a qr code for example, to set the ip before distributing the code, and it would all work smoothly @@ -665,13 +678,13 @@ socket = io("http://"+ip,{ socket.on("songAdd", function(data) { // console.log("recieved data from songAdd"); - console.log(data); + // console.log(data); addToPlaylist(data); }) socket.on("timeUpdate", function(data) { // console.log("recieved data from timeUpdate"); - console.log(data); + // console.log(data); playlistElapsedSeconds = data["elapsedTime"]; currentlyPlaying = data["playingState"] }); @@ -682,4 +695,14 @@ socket.on("skipSong",() => { } else { justSkipped = false; } -}) \ No newline at end of file +}) + +socket.on("settingsChange",(data) => { + console.log(data); + if(justChangedSetting) { + console.log("working"); + justChangedSetting = false; + } else { + checkSettings(); + } +}); \ No newline at end of file diff --git a/Client/styles.css b/Client/styles.css index 2c2f4c9..70bd991 100644 --- a/Client/styles.css +++ b/Client/styles.css @@ -62,6 +62,7 @@ h4 { left: 50%; bottom: 0; margin: 0 auto; + transform: translateX(-50%); background-color:var(--bg-main); } diff --git a/Server/webbyBits.py b/Server/webbyBits.py index f78961c..22a238c 100644 --- a/Server/webbyBits.py +++ b/Server/webbyBits.py @@ -190,6 +190,7 @@ def settingsControl(): volumeLevel = int(recieveData["level"]) if(volumeLevel <= 100 and volumeLevel >= 0): volumePassed = player.audio_set_volume(volumeLevel) + socketio.emit("settingsChange") return {"error":"ok","data":{"volumePassed":volumePassed}},200 else: return {"error":"Invalid volume level","data":None},422 @@ -198,11 +199,13 @@ def settingsControl(): elif recieveData["setting"] == "partymode-toggle": if ADMIN_PASS == recieveData['password'] or controlPerms["PM"]: partyMode = not(partyMode) + socketio.emit("settingsChange") return ERR_200 else: return ERR_NO_ADMIN elif recieveData["setting"] == "perms": if ADMIN_PASS == recieveData["password"]: + socketio.emit("settingsChange") controlPerms = recieveData["admin"] # print(recieveData["admin"]) return ERR_200 From 8cb8b6139793252c28ee7807687ac31c87f84689 Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:21:02 -0500 Subject: [PATCH 07/17] Update wishlist.md --- wishlist.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wishlist.md b/wishlist.md index 8be1c9d..50eee2d 100644 --- a/wishlist.md +++ b/wishlist.md @@ -27,7 +27,5 @@ - [x] Set a timeout to change the time (to start) - [x] Send updates to the playlist in real time when songs are added * This is only kind of done, still needs work - - [ ] Update the playlist's html without destroying it (create 1 new element) - - [x] Tell clients looking at the playlist when the song has been paused (so they can pause the local timers) - * Again, still needs work - * This is currently solved by just sending the time and "playing status" once a second-ish \ No newline at end of file + - [x] Update the playlist's html without destroying it (create 1 new element) + - [x] Tell clients looking at the playlist when the song has been paused (so they can pause the local timers) \ No newline at end of file From 95efd937f6a5857996b8a0e45d2d8ff4d5c6dda9 Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:22:07 -0500 Subject: [PATCH 08/17] Update wishlist.md --- wishlist.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wishlist.md b/wishlist.md index 50eee2d..9bf1ffb 100644 --- a/wishlist.md +++ b/wishlist.md @@ -23,9 +23,9 @@ - Potentially a "redemption code" system, which can be tracked client side - All of this is very hackable without a server-side login. - [ ] Websockets / some method of updating the time remaining to any client on the playlist screen - * This is implemented in a very broken way right now - [x] Set a timeout to change the time (to start) - [x] Send updates to the playlist in real time when songs are added - * This is only kind of done, still needs work - [x] Update the playlist's html without destroying it (create 1 new element) - - [x] Tell clients looking at the playlist when the song has been paused (so they can pause the local timers) \ No newline at end of file + - [x] Tell clients looking at the playlist when the song has been paused (so they can pause the local timers) + - [x] Settings updates + - [ ] Without re-posting the server (contain update data in websocket ping) \ No newline at end of file From 86a37a89c6fa11924d5d8a4f2bea4948a0e81d2c Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Sun, 1 Feb 2026 23:35:47 -0500 Subject: [PATCH 09/17] secure getFromServer is possible --- Client/scripts.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Client/scripts.js b/Client/scripts.js index 7d68143..46da7c2 100644 --- a/Client/scripts.js +++ b/Client/scripts.js @@ -50,9 +50,11 @@ async function getFromServer(bodyInfo, source="", secure=false, password=adminPa } let href = ""; if(secure) { - href = "https://"+ip+"/" + href = "https://"+ip+"/" + source; + } else { + href = "http://"+ip+"/" + source; } - const response = await fetch("http://"+ip+"/"+source, { + const response = await fetch(href, { method: "POST", body: JSON.stringify(bodyInfo), headers: { From 6effff1dc5f8a96765b06f627311d93d32d14820 Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:58:31 -0500 Subject: [PATCH 10/17] Adding a placeholder for future socket stuff --- Client/scripts.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Client/scripts.js b/Client/scripts.js index 46da7c2..cda16b4 100644 --- a/Client/scripts.js +++ b/Client/scripts.js @@ -296,6 +296,17 @@ async function displayElapsedPlaylistTime(elapsed=0,length=-1) { } } +async function updateSingleSetting(data) { + let toBeChanged = data["settingToChange"]; + if (toBeChanged === "partymode") { + + } else if (toBeChanged === "perms") { + + } else if (toBeChanged === "volume") { + + } +} + async function checkSettings(skipServer=false) { //check client stuff first so if the server doesn't exist it can still be changed and seen if (ip.slice(-5)=="19054") { From d8b261dcb7da897c76ad48e4d2c61dd803dc29d2 Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Mon, 9 Feb 2026 10:12:38 -0500 Subject: [PATCH 11/17] Adjusted details relating to new features --- readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b730a50..5f67c5b 100644 --- a/readme.md +++ b/readme.md @@ -60,7 +60,6 @@ These are specific details on each section of the app, and how to use them - Accepts Play-Pause and Skip commands - Uses port 19054 by default - Can be changed in the `.env` file - - The default port can be changed in the file - Running with `--admin (admin password)` sets an admin password for moderation on the client - ***Note: Do not reuse a password, the password is hashed before being sent over the network, but I still wouldn't bet my house on it, no security is guaranteed*** - Anyone who knows the admin password can enter it on the client and change the abilities of any non-admin users (for example to limit skipping) @@ -70,6 +69,9 @@ These are specific details on each section of the app, and how to use them - Add track to queue - Partymode toggle - Change volume + - Add duplicate track to queue + - This is a seperate toggle, but is based on the setting "Add track to queue" + - Basically if you can't add at all, you can't add a duplicate either (obviously) - When this argument is left out (or empty string) the admin features aren't used, and everyone can do everything ### Client: @@ -89,6 +91,7 @@ From left to right: - Volume controls the VLC volume of the connected server - *Because the volume can be controlled in the client, for best usage set your device volume as high as possible and turn it down using this slider* - QR code to allow others to connect to and use the Remote + - Admin password can be set to restrict actions for general users, or avoid the set restrictions ### A quick note on the password feature From 37bdd33aff2126f4bad9d81917816b5de1280b84 Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:04:45 -0500 Subject: [PATCH 12/17] Removed a false comment --- Client/scripts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/scripts.js b/Client/scripts.js index cda16b4..9b13cad 100644 --- a/Client/scripts.js +++ b/Client/scripts.js @@ -67,9 +67,9 @@ async function getFromServer(bodyInfo, source="", secure=false, password=adminPa // im suprised i didn't comment on this already but this is kinda lame desing // its not wrong but you know // it is easy which i like - // and it overrides any other non-async alerts which is nice alertText("Error: Admin restricted action") } else if(!response.ok){ + throw new Error(data.error); alertText("Error: "+data.error); } // we add some information from the response just in case it is needed @@ -502,7 +502,7 @@ async function generateVisualPlaylist(conditions="") { async function submitSong(songid) { let returncode = await getFromServer({song: songid}, "songadd"); - if(returncode == ERR_NO_ADMIN) { + if(returncode["status"] === ERR_NO_ADMIN) { // right now the error is alerted in getFromServer, maybe will change that } else if(returncode["status"]!==200) { alertText("That song's already in the queue! Hang on!") From 566ce9cd73b0328e78323c5a449ec483da919f1e Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Wed, 11 Feb 2026 09:06:02 -0500 Subject: [PATCH 13/17] Sockets are finished to an acceptable level --- Client/index.html | 2 +- Client/scripts.js | 18 +++++++++++++----- Server/webbyBits.py | 13 +++++++++---- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Client/index.html b/Client/index.html index 591a402..4fa3aac 100644 --- a/Client/index.html +++ b/Client/index.html @@ -104,7 +104,7 @@ changes visibility with JS-->


-
+
diff --git a/Client/scripts.js b/Client/scripts.js index cda16b4..e25604c 100644 --- a/Client/scripts.js +++ b/Client/scripts.js @@ -299,11 +299,17 @@ async function displayElapsedPlaylistTime(elapsed=0,length=-1) { async function updateSingleSetting(data) { let toBeChanged = data["settingToChange"]; if (toBeChanged === "partymode") { - + document.getElementById("partymode-button").textContent = data["newData"]; } else if (toBeChanged === "perms") { - + let currentAdminPerms = data["newData"]; + document.getElementById("addsongsettingcheckbox").checked = currentAdminPerms["AS"]; + document.getElementById("skipsongsettingcheckbox").checked = currentAdminPerms["SK"]; + document.getElementById("playpausesettingcheckbox").checked = currentAdminPerms["PP"]; + document.getElementById("partymodesettingcheckbox").checked = currentAdminPerms["PM"]; + document.getElementById("volumechangesettingcheckbox").checked = currentAdminPerms["VOL"]; + document.getElementById("duplicateallowesettingcheckbox").checked = currentAdminPerms["DUP"]; } else if (toBeChanged === "volume") { - + document.getElementById("volumerange").value = data["newData"]; } } @@ -608,9 +614,10 @@ document.addEventListener('keydown', function(e){ }}) document.getElementById("playlist-mode").style.display = "none"; document.getElementById("settings-mode").style.display = "none"; -document.getElementById("volumerange").onchange = async function() { +document.getElementById("volumerange").onchange = async function(e) { // there is no reason for this not to be a defined function // FIX THIS + console.log(e); let returnValue = await getFromServer({setting:"volume",level:this.value}, "settings") if (returnValue["status"] == ERR_NO_ADMIN) { // alertText("Error: Admin restricted action"); @@ -716,6 +723,7 @@ socket.on("settingsChange",(data) => { console.log("working"); justChangedSetting = false; } else { - checkSettings(); + // checkSettings(); + updateSingleSetting(data); } }); \ No newline at end of file diff --git a/Server/webbyBits.py b/Server/webbyBits.py index 22a238c..8342e75 100644 --- a/Server/webbyBits.py +++ b/Server/webbyBits.py @@ -190,8 +190,12 @@ def settingsControl(): volumeLevel = int(recieveData["level"]) if(volumeLevel <= 100 and volumeLevel >= 0): volumePassed = player.audio_set_volume(volumeLevel) - socketio.emit("settingsChange") - return {"error":"ok","data":{"volumePassed":volumePassed}},200 + if(volumePassed == 0): + # only emit a signal i the volume really changed + socketio.emit("settingsChange",{"settingToChange":"volume","newData":volumeLevel}) + return {"error":"ok","data":{"volumePassed":volumePassed}},200 + else: + return {"error":"VLC cannot take volume change requests at this time","data":None},500 else: return {"error":"Invalid volume level","data":None},422 else: @@ -199,15 +203,16 @@ def settingsControl(): elif recieveData["setting"] == "partymode-toggle": if ADMIN_PASS == recieveData['password'] or controlPerms["PM"]: partyMode = not(partyMode) - socketio.emit("settingsChange") + partyModeStr = "On" if partyMode else "Off" + socketio.emit("settingsChange",{"settingToChange":"partymode","newData":partyModeStr}) return ERR_200 else: return ERR_NO_ADMIN elif recieveData["setting"] == "perms": if ADMIN_PASS == recieveData["password"]: - socketio.emit("settingsChange") controlPerms = recieveData["admin"] # print(recieveData["admin"]) + socketio.emit("settingsChange",{"settingToChange":"perms","newData":controlPerms}) return ERR_200 else: return ERR_NO_ADMIN From f2204ee7ed3425cf8e59d17870fc4f40994911d1 Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Wed, 11 Feb 2026 09:12:00 -0500 Subject: [PATCH 14/17] Add versions and socketio --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ca37329..7b0c3f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,10 @@ certifi==2024.2.2 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 -dotenv +dotenv==0.9.9 Flask==3.0.3 Flask-Cors==4.0.1 +Flask-SocketIO==5.6.0 idna==3.7 itsdangerous==2.2.0 Jinja2==3.1.4 From f556f17cce34c74222a79bec115e1aea8c34ac50 Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Wed, 11 Feb 2026 09:36:19 -0500 Subject: [PATCH 15/17] Few fixes that i'm going to un-re-fix later --- Client/scripts.js | 11 ++++++----- Server/webbyBits.py | 4 +--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Client/scripts.js b/Client/scripts.js index 5cb324c..a330653 100644 --- a/Client/scripts.js +++ b/Client/scripts.js @@ -148,7 +148,7 @@ async function controlButton(buttonType) { document.getElementById("songlist-mode").style.display = "none"; document.getElementById("settings-mode").style.display = "block"; checkSettings() - } else if (buttonType = "pm") { //Partymode toggle (in settings) + } else if (buttonType == "pm") { //Partymode toggle (in settings) let response = await getFromServer({setting: "partymode-toggle"}, "settings") if(response.ok) { justChangedSetting = true; @@ -156,6 +156,8 @@ async function controlButton(buttonType) { } else { // dont think anything is needed here } + } else { + alertText("Error: You pushed a button that does not exist"); } @@ -617,8 +619,7 @@ document.getElementById("settings-mode").style.display = "none"; document.getElementById("volumerange").onchange = async function(e) { // there is no reason for this not to be a defined function // FIX THIS - console.log(e); - let returnValue = await getFromServer({setting:"volume",level:this.value}, "settings") + let returnValue = await getFromServer({setting:"volume",level:e.target.value}, "settings") if (returnValue["status"] == ERR_NO_ADMIN) { // alertText("Error: Admin restricted action"); // there's an admin restrict alert built into getFromServer @@ -718,9 +719,9 @@ socket.on("skipSong",() => { }) socket.on("settingsChange",(data) => { - console.log(data); + // console.log(data); if(justChangedSetting) { - console.log("working"); + // console.log("working"); justChangedSetting = false; } else { // checkSettings(); diff --git a/Server/webbyBits.py b/Server/webbyBits.py index 8342e75..25e245b 100644 --- a/Server/webbyBits.py +++ b/Server/webbyBits.py @@ -193,9 +193,7 @@ def settingsControl(): if(volumePassed == 0): # only emit a signal i the volume really changed socketio.emit("settingsChange",{"settingToChange":"volume","newData":volumeLevel}) - return {"error":"ok","data":{"volumePassed":volumePassed}},200 - else: - return {"error":"VLC cannot take volume change requests at this time","data":None},500 + return {"error":"ok","data":{"volumePassed":volumePassed}},200 else: return {"error":"Invalid volume level","data":None},422 else: From 725cc3eb89c839b88e0842925d1d28100d8e031c Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Wed, 11 Feb 2026 09:36:32 -0500 Subject: [PATCH 16/17] Re-freeze pip with minimum requirements --- requirements.txt | Bin 303 -> 930 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7b0c3f99893315b19b02dce1572dc86a1f9ec42d..31537619c81b0adb7515c103e17411abccb798c0 100644 GIT binary patch literal 930 zcmZva%}>Hm5XARv;-6AN%ZGCCYNCnp;K6e#j6)xfAxwO6a z%b9dfyn0V}ik`q7c;#Gk=UpvAgOpazAWtD5p-v2uy?~NVxqGl@M~_@do70j!<4#BH z`Gf?Oe~A`32s%RYE4V6ZDbB!OEeFg*H(-asa2ENu=?-0#rV*cP2B?(z;L zHv{*)re0Ikq|)o!^U!X(DO>vlvBYV~2LGljv=jyHmsl|_Tw~66&3?rOLfjk3544b!$>8Kt*=oI|X^#ta{{A tCnVpHY{72@a?o+=orI~ca437d@P-UdJ8owln6L$-w;GZWyWT-~i@!tOhCKiP literal 303 zcmXv}yKciU4BYiECiHM#1E&H7S|I3Fgod!eQ)*oe7gJ5p@J`d#(kzVkK3?h)MDLrbNNV7eDt@^7kXa|?&y&u0Ci N#3%9eq%!_@{{gJmSpWb4 From 192a84deeda6cc382b6cc922997d2916dd50115b Mon Sep 17 00:00:00 2001 From: Kristy Fournier <124598538+kristy-fournier@users.noreply.github.com> Date: Wed, 11 Feb 2026 09:38:45 -0500 Subject: [PATCH 17/17] Update Wishlist, sockets are done --- wishlist.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wishlist.md b/wishlist.md index 9bf1ffb..e561f40 100644 --- a/wishlist.md +++ b/wishlist.md @@ -22,10 +22,10 @@ - Without a login system there's no easy way to give credits to specific clients (and a login is beyond scope of what I want to do) - Potentially a "redemption code" system, which can be tracked client side - All of this is very hackable without a server-side login. -- [ ] Websockets / some method of updating the time remaining to any client on the playlist screen +- [x] Websockets / some method of updating the time remaining to any client on the playlist screen - [x] Set a timeout to change the time (to start) - [x] Send updates to the playlist in real time when songs are added - [x] Update the playlist's html without destroying it (create 1 new element) - [x] Tell clients looking at the playlist when the song has been paused (so they can pause the local timers) - [x] Settings updates - - [ ] Without re-posting the server (contain update data in websocket ping) \ No newline at end of file + - [x] Without re-posting the server (contain update data in websocket ping) \ No newline at end of file