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