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
--
2.49.1
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);
--
2.49.1
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
--
2.49.1
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
--
2.49.1
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/
--
2.49.1
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
--
2.49.1
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
--
2.49.1
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
--
2.49.1
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: {
--
2.49.1
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") {
--
2.49.1
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
--
2.49.1
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!")
--
2.49.1
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
--
2.49.1
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
--
2.49.1
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:
--
2.49.1
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|I3
Fgod!eQ)*oe7gJ5p@J`d#(kzVkK3?h)MDLrbNNV7eDt@^7kXa|?&y&u0Ci
N#3%9eq%!_@{{gJmSpWb4
--
2.49.1
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
--
2.49.1