diff --git a/.gitignore b/.gitignore
index fcb12f0..245e57a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
server/sound/
-server/songDatabase.json
\ No newline at end of file
+server/songDatabase.json
+start.bat
\ No newline at end of file
diff --git a/Client/favicon.ico b/Client/favicon.ico
new file mode 100644
index 0000000..bbe20ae
Binary files /dev/null and b/Client/favicon.ico differ
diff --git a/Client/images/placeholder.png b/Client/images/placeholder.png
new file mode 100644
index 0000000..a684493
Binary files /dev/null and b/Client/images/placeholder.png differ
diff --git a/Client/images/play-pause.png b/Client/images/play-pause.png
new file mode 100644
index 0000000..3796891
Binary files /dev/null and b/Client/images/play-pause.png differ
diff --git a/Client/images/playlist.png b/Client/images/playlist.png
new file mode 100644
index 0000000..e9ff72a
Binary files /dev/null and b/Client/images/playlist.png differ
diff --git a/Client/images/search.png b/Client/images/search.png
new file mode 100644
index 0000000..b3f699a
Binary files /dev/null and b/Client/images/search.png differ
diff --git a/Client/images/settings.png b/Client/images/settings.png
new file mode 100644
index 0000000..bb23a10
Binary files /dev/null and b/Client/images/settings.png differ
diff --git a/Client/images/skip.png b/Client/images/skip.png
new file mode 100644
index 0000000..032beda
Binary files /dev/null and b/Client/images/skip.png differ
diff --git a/Client/index.html b/Client/index.html
new file mode 100644
index 0000000..ffda3ea
--- /dev/null
+++ b/Client/index.html
@@ -0,0 +1,97 @@
+
+
+
+ Jukebox Controller
+
+
+
+
+
+
+
+
+
+
Jukebox Remote
+
Add songs to the shared playlist below!
+
+
+
+
+
+
+
+
+
Search to find songs!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Client Settings (Saved to device)
+
+
+
Server IP:
+
IP of the device running the song server
+
+
+
+
Alert Time:
+
How long alerts stay on screen for (seconds)
+
+
+
Server Settings (Saved to server)
+
+
Party Mode:
+
Add random songs to the queue when it is about to be empty
+
+
+
+
Volume:
+
Volume of the music
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Client/manifest.json b/Client/manifest.json
new file mode 100644
index 0000000..7ab15d3
--- /dev/null
+++ b/Client/manifest.json
@@ -0,0 +1,15 @@
+{
+ "name": "Jukebox Remote",
+ "short_name": "Jukebox Remote",
+ "start_url": "index.html",
+ "display": "standalone",
+ "background_color": "#eeeeee",
+ "theme_color": "#eeeeee",
+ "orientation": "portrait-primary",
+ "icons": [
+ {
+ "src": "/favicon.ico",
+ "type": "image/ico", "sizes": "100x100"
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/Client/scripts.js b/Client/scripts.js
new file mode 100644
index 0000000..2058852
--- /dev/null
+++ b/Client/scripts.js
@@ -0,0 +1,331 @@
+let ip
+let alertTime = 2
+async function alertText(text="Song Added!") {
+ alertbox = document.getElementById("alert");
+ alertbox.innerHTML = text;
+ await new Promise(r => setTimeout(r, alertTime*1000));
+ if (alertbox.innerHTML == text) {
+ alertbox.innerHTML = ""
+ }
+}
+// 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="") {
+ try{
+ const response = await fetch("http://"+ip+"/"+source, {
+ method: "POST",
+ body: JSON.stringify(bodyInfo),
+ headers: {
+ "Content-type": "application/json; charset=UTF-8"
+ }
+ });
+ const data = await response.json();
+ return await data;
+ } catch(e) {
+ if (e == "TypeError: Failed to fetch"){
+ alertText("error: NoConnect to Server (is the ip set?)")
+ } else {
+ alertText("error: " + e)
+ }
+ const response=null;
+ return response;
+ }
+}
+
+
+//cookie reader is taken from internet because cookies ae too complicated for me
+//i still understand how it works though promise just i see no reason to write this from scratch
+function getCookie(cname) {
+ let name = cname + "=";
+ let decodedCookie = decodeURIComponent(document.cookie);
+ let ca = decodedCookie.split(';');
+ for(let i = 0; i ";
+ document.getElementById("playlist-mode").style.display = "block";
+ document.getElementById("songlist-mode").style.display = "none";
+ document.getElementById("settings-mode").style.display = "none";
+ generateVisualPlaylist();
+ } else if (buttonType == "se") {
+ document.getElementById("songlist").innerHTML = "
";
+ }
+}
+function alertTimeEnter(e){
+ if (e.key == "Enter") {
+ e.preventDefault();
+ alertTimeSet(document.getElementById("alerttimetextbox").value);
+ }
+}
+
+function alertTimeSet(time) {
+ alertTime = time;
+ document.cookie = "alertTime="+alertTime+"; path=/;"
+ alertText("Alerts stay on screen for " + alertTime.toString() + " seconds")
+}
+
+function ipSetEnter(e){
+ if (e.key == "Enter") {
+ e.preventDefault();
+ ipSetter(document.getElementById("iptextbox").value)
+ }
+}
+
+function ipSetter(){
+ ipBox = document.getElementById("iptextbox").value
+ if (ipBox == "") {
+ alertText("Your IP is set to "+ip)
+ } else {
+ if (ipBox.includes(":")) {
+ port = ipBox.slice(ipBox.indexOf(":")+1)
+ ip = ipBox;
+ document.cookie = "ip="+ip+"; path=/;"
+ alertText("Your IP is now set to "+ip.slice(0, ipBox.indexOf(":"))+" at port "+port)
+ } else {
+ ip = ipBox + ":19054"
+ document.cookie = "ip="+ip+"; path=/;"
+ alertText("Your IP is now set to "+ipBox+" at port 19054 (Default)")
+ }
+ }
+
+}
+
+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") {
+ document.getElementById("iptextbox").value = ip.slice(0,-6)
+ } else {
+ document.getElementById("iptextbox").value = ip;
+ }
+ document.getElementById("alerttimetextbox").value = alertTime
+ partyButtonState = document.getElementById("partymode-button").innerHTML;
+ x = await getFromServer({setting: "getsettings"}, "settings");
+ if (!(skipServer) || partyButtonState=="N/A") {
+ if (x["partymode"] == false) {
+ document.getElementById("partymode-button").innerHTML = "Off";
+ } else {
+ document.getElementById("partymode-button").innerHTML = "On";
+ }
+ } else if (document.getElementById("partymode-button").innerHTML == "Off") {
+ document.getElementById("partymode-button").innerHTML = "On";
+ } else {
+ document.getElementById("partymode-button").innerHTML = "Off";
+ }
+ document.getElementById("volumerange").value = parseInt(x["volume"])
+}
+
+async function generateVisualPlaylist(conditions="") {
+ document.getElementById("playlist").innerHTML = "";
+ playlist = await getFromServer(null, "playlist");
+ if (playlist.length==0){
+ document.getElementById("playlist-alert").innerHTML = "Nothing's Queued..."
+ } else {
+ if (conditions=="skip-button") {
+ playlist.shift()
+ if (playlist.length==0){
+ document.getElementById("playlist-alert").innerHTML = "Nothing's Queued..."
+ }
+ }
+ for (i in playlist) {
+ let newItem = document.createElement("div");
+ newItem.className = "item";
+ newItem.id = playlist[i]["file"];
+ let image = document.createElement("img");
+ try {
+ if (playlist[i]["art"] == null) {
+ throw "no image lolz"
+ }
+ image.src = playlist[i]["art"];
+ } catch(err){
+ image.src = "./images/placeholder.png";
+ }
+ image.id = String(playlist[i]["file"])+" image";
+ let head3 = document.createElement("h3");
+ head3.innerText = playlist[i]["title"];
+ let head4 = document.createElement("h4");
+ head4.innerText=playlist[i]["artist"];
+ let head5 = document.createElement("h5");
+ let timeLeft =document.createElement("h5");
+ timeLeft.style.fontWeight = 100;
+ try {
+ if (i == 0) {
+ 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){
+ console.log(err)
+ }
+ 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);
+ }
+ }
+}
+
+async function submitSong(songid) {
+ getFromServer({song: songid}, "songadd")
+ alertText("Added to Queue")
+}
+function checkWhatSongWasClicked(e) {
+ itemId = e.srcElement.id;
+ if ((itemId.length-itemId.lastIndexOf("image") == 5) && itemId.lastIndexOf("image")!=-1) {
+ itemId = itemId.slice(0,-6)
+ }
+ //i feel like later dylan won't apreciate this
+ //one of my files was "file.MP3" so it didn't work
+ if (itemId.slice(-4).toLowerCase() == ".mp3") {
+ submitSong(itemId);
+ }
+}
+
+let optionslist = []
+
+//sets all de stuff for buttons
+document.addEventListener('keydown', function(e){
+ if (e.key == "/"){
+ document.getElementById("title").scrollIntoView();
+ document.getElementById("songsearch").select();
+ e.preventDefault()
+}})
+document.getElementById("playlist-mode").style.display = "none";
+document.getElementById("settings-mode").style.display = "none";
+//.ontouch for mobile??
+document.getElementById("volumerange").onchange = function() {
+ getFromServer({setting:"volume",level:this.value}, "settings")
+ if (this.value == 0) {
+ alertText("The volume is now set to 0 (Pause?)")
+ } else {
+ alertText("The volume is now set to " + this.value.toString())
+ }
+
+}
+document.getElementById("settings-button").addEventListener('click',function(){controlButton("st")});
+document.getElementById("play-pause-button").addEventListener('click', function(){controlButton("pp")});
+document.getElementById("playlist-button").addEventListener('click', function(){controlButton("pl")});
+document.getElementById("search-button").addEventListener('click', function(){controlButton("se")});
+document.getElementById("skip-button").addEventListener('click',function(){controlButton("sk")});
+document.getElementById("go-search").addEventListener('click', function(){searchSongs(document.getElementById("songsearch").value)})
+document.getElementById("songsearch").addEventListener('keydown', function(e){searchSongsEnter(e)});
+document.getElementById("iptextbox").addEventListener('keydown', function(e){ipSetEnter(e)});
+document.getElementById("alerttimetextbox").addEventListener('keydown', function(e){alertTimeEnter(e)});
+document.getElementById("partymode-button").addEventListener('click',function(){controlButton("pm")})
+//sets the fact that clicking a song needs to return its id to the function to find it
+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";
+//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
+//example (http://192.168.1.100:8000/?ip=192.168.1.100:19054 sets the ip to the same host at the default port)
+//the port must be set manually using this method, but only has to be done once for the url that ends up being shared
+let params = new URLSearchParams(location.search);
+//tries the url first, then the cookie, then the default
+ip = params.get("ip")
+if (ip == null || ip=="") {
+ ip=getCookie("ip")
+}
+console.log(ip)
+if (ip==null || ip==""){
+ ip = ""
+}
+document.cookie = "ip="+ip+"; path=/;"
+
+
+alertTime = getCookie("alertTime")
+document.getElementById("alerttimetextbox").value = alertTime
+if (alertTime == "") {
+ alertTime = 2;
+ document.cookie = "alertTime="+alertTime+"; path=/;"
+}
\ No newline at end of file
diff --git a/Client/styles.css b/Client/styles.css
new file mode 100644
index 0000000..71db5a7
--- /dev/null
+++ b/Client/styles.css
@@ -0,0 +1,170 @@
+/* testing */
+
+/* Things that are always visible */
+
+body {
+ background-color: #EEEEEE;
+}
+* {
+ font-family: 'arial';
+}
+.italic {
+ font-style: italic;
+}
+h4 {
+ font-weight: 100;
+}
+.clear{
+ clear: both;
+ display: block;
+ content: "";
+ width: 100%;
+ }
+.controls {
+ max-width: 550px;
+ min-width: 300px;
+ position: fixed;
+ width:100%;
+ left: 50%;
+ bottom: 0;
+ margin: 0 auto;
+ background-color:inherit;
+}
+
+.alert {
+ position: fixed;
+ bottom: 10%;
+ width: 100%;
+ text-align: center;
+ z-index: 1000;
+ background-color: #EEEEEEd6;
+}
+
+.settings-button {
+ width: 15%;
+ max-width: 90px;
+ position:fixed;
+ top:0;
+ right:0;
+ margin: 3px;
+ background:inherit;
+ /* This is a circle background for the circle settings button
+ So it can display over other text and such */
+ border-radius: 50%;
+}
+
+.control-button{
+ width:20%;
+ max-width: 110px;
+ margin: auto 2%;
+}
+
+.intro {
+ width: 300px;
+ margin: auto;
+ text-align: center;
+}
+
+/* Songlist stuff */
+.songlist {
+ width: 60%;
+ min-width: 300px;
+ margin:auto auto 150px;
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.songlist > .item{
+ border: 1px solid #333333;
+ width:30%;
+ max-width: 200px;
+ margin: 5px auto;
+ min-width: 100px;
+ background-color: inherit;
+}
+
+.songlist > .item > img{
+ max-width:200px;
+ width:100%
+}
+
+.songlist > .item > h3, .songlist > .item > h4{
+ margin-left: 2px;
+ margin-right: 2px;
+}
+
+.searchbox-holder {
+ width: 20%;
+ margin: 20px auto 0;
+ min-width: 250px;
+
+}
+.searchbox {
+ width: 65%;
+ margin: 1px;
+}
+.go-search {
+ width: 20%;
+ min-width: 50px;
+}
+/* playlist mode stuff */
+
+.playlist {
+ width: 60%;
+ min-width: 300px;
+ margin:auto auto 150px;
+}
+
+.playlist > .item{
+ border: 1px solid #333333;
+ display: flex;
+ max-width: 50em;
+ min-width: 200px;
+ margin: 5px auto;
+ height: auto;
+}
+
+.playlist > .item > .text {
+ display: inline-block;
+ margin: 0px 3px;
+}
+
+.playlist > .item > img {
+ display: inline-flex;
+ max-width: 100px;
+ width:30%;
+ margin: 0;
+ aspect-ratio: 1/1;
+}
+
+.playlist > .item > .text > * {
+ margin:5% 2px;
+}
+
+/* settings stuff */
+
+.settings {
+ width: 95%;
+ margin: auto auto 150px;
+ max-width: 600px;
+}
+
+.settings > .item {
+ margin-left: 10%;
+ width:fit-content;
+
+}
+
+.settings > .item:not(:last-child) {
+ padding-bottom: 10px;
+ border-bottom: 1px solid #333333;
+}
+
+.settings > .lastSet1 {
+ border-bottom: 0;
+}
+
+#volumerange {
+ background-color: #4477AA;
+ color: #4477ff;
+}
\ No newline at end of file
diff --git a/Screenshot_MAIN.png b/Screenshot_MAIN.png
new file mode 100644
index 0000000..afa7676
Binary files /dev/null and b/Screenshot_MAIN.png differ
diff --git a/Screenshot_RAW.png b/Screenshot_RAW.png
deleted file mode 100644
index 381e29b..0000000
Binary files a/Screenshot_RAW.png and /dev/null differ
diff --git a/Server/databaseGenerator.py b/Server/databaseGenerator.py
new file mode 100644
index 0000000..19f1b67
--- /dev/null
+++ b/Server/databaseGenerator.py
@@ -0,0 +1,77 @@
+import os
+from mutagen.easyid3 import EasyID3
+from mutagen.mp3 import MP3
+import requests, ast, time, math, argparse, json
+# place your lastfm key in the slot below
+apikeylastfm="YourLastfmKeyHere"
+
+loading = ["-","\\","|","/"]
+songFiles = os.listdir(r'./sound')
+parser=argparse.ArgumentParser(description="Options for the generation of the song database")
+parser.add_argument('-m', '--mode', help='new/update: Remake database or update current', default= "update")
+parser.add_argument('-a', '--art', help="True/False: Add art to the database using LastFm (takes minimum 0.25s per song)", default="True")
+args = parser.parse_args()
+if args.mode == "update":
+ try:
+ with open('songDatabase.json', 'r') as handle:
+ songDatabaseList = json.load(handle)
+ except:
+ songDatabaseList=[]
+
+ for i in songDatabaseList:
+ try:
+ songFiles.index(i["file"]) != -1
+ except:
+ print("deleted: " + i["file"] + " from database")
+ songDatabaseList.pop(songDatabaseList.index(i))
+
+ for i in songDatabaseList:
+ songFiles.pop(songFiles.index(i["file"]))
+ print("new songs: " + str(songFiles))
+elif args.mode=="new":
+ songDatabaseList = []
+
+if args.art.lower() == "true":
+ x = len(songFiles)*0.25
+ if x > 60:
+ print("ETA "+ str(x/60) + " minutes")
+ else:
+ print("ETA "+ str(x) + " seconds")
+
+for i in songFiles:
+ try:
+ song = EasyID3("sound/"+i)
+ title = song['title'][0]
+ artist = song['artist'][0]
+ except:
+ try:
+ song = i.split("_")
+ title = song[0]
+ artist = song[1].split(".")[0]
+ except:
+ title = i
+ artist = None
+ if args.art.lower() == "true":
+ try:
+ image = ast.literal_eval(requests.post(url="http://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key="+apikeylastfm+"&artist="+artist+"&track="+title+"&format=json").text)["track"]["album"]["image"][1]["#text"]
+ if image == "":
+ image = ast.literal_eval(requests.post(url="http://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key="+apikeylastfm+"&artist="+artist+"&track="+title+"&format=json").text)["track"]["album"]["image"][2]["#text"]
+ if image == "":
+ image = None
+ time.sleep(0.25)
+ except:
+ image=None
+ else:
+ image=None
+ try:
+ length = math.ceil(MP3("sound/"+i).info.length)
+ except:
+ length = 0
+ if len(songFiles) != 1:
+ index = (songFiles.index(i))%4
+ print("\r" + str(loading[index] + str(math.floor((songFiles.index(i)/(len(songFiles)-1))*100))+ "%"), end='', flush=True)
+ # each "song" is stored as a dictionary containing the below stuff, and each dictionary is put into a list
+ songDatabaseList.append({"file":i,"title":title,"artist":artist,"art":image,"length":length})
+
+with open('songDatabase.json', 'w') as handle:
+ json.dump(songDatabaseList, handle)
diff --git a/Server/webbyBits.py b/Server/webbyBits.py
new file mode 100644
index 0000000..6401a69
--- /dev/null
+++ b/Server/webbyBits.py
@@ -0,0 +1,128 @@
+from flask import Flask
+from flask import request
+from flask_cors import CORS
+import json,vlc,csv,threading,time,random
+random.seed()
+global partyMode
+global skipNow
+global songNext
+partyMode = False
+songNext = None
+skipNow = False
+playlist = []
+playlistLock = threading.Lock()
+fakeplayer = vlc.Instance()
+player = fakeplayer.media_player_new()
+# for client side volume to work as well as possible, set system volume to 100 and control in app
+player.audio_set_volume(100)
+app = Flask(__name__)
+CORS(app)
+with open('./songDatabase.json', 'r') as handle:
+ songDatabaseList = json.load(handle)
+
+def queueSong(song):
+ with playlistLock:
+ playlist.append(song)
+# this is a loop that plays the songs and checks for playlist changes, skips, ect.
+def playQueuedSongs():
+ global skipNow
+ global songNext
+ global partyMode
+ while True:
+ with playlistLock:
+ z = str(player.get_state())
+
+ if playlist and (z == "State.Ended" or z== "State.Stopped" or z == "State.NothingSpecial" or skipNow == True):
+ player.stop()
+ skipNow = False
+ songNext = playlist.pop(0)
+ media = fakeplayer.media_new("sound/"+songNext)
+ player.set_media(media)
+ player.play()
+ elif (len(playlist) == 0) and skipNow==True:
+ skipNow=False
+ songNext = None
+ player.stop()
+ elif (len(playlist) == 0) and (z == "State.Ended" or z == "State.NothingSpecial" or z=="State.Stopped"):
+ songNext = None
+ elif (len(playlist)<1) and (partyMode == True):
+ playlist.append(random.choice(songDatabaseList)["file"])
+ time.sleep(1)
+
+queueThread = threading.Thread(target=playQueuedSongs)
+queueThread.daemon = True
+queueThread.start()
+
+@app.route("/controls", methods=['POST'])
+def playerControls():
+ global skipNow
+ global media
+ global partyMode
+ recieveData=request.get_json(force=True)
+ if recieveData["control"] != None:
+ if recieveData["control"] == "play-pause":
+ player.pause()
+ return "200"
+ elif recieveData["control"] == "skip":
+ skipNow = True
+ # print(str(player.get_state()))
+ return "200"
+ else:
+ return "400"
+
+@app.route("/settings", methods=['POST'])
+def settingsControl():
+ global partyMode
+ recieveData = request.get_json(force=True)
+ if recieveData["setting"] == "volume":
+ player.audio_set_volume(int(recieveData["level"]))
+ return "200"
+ elif recieveData["setting"] == "getsettings":
+ x = {"partymode":partyMode,"volume":player.audio_get_volume()}
+ return x
+ elif recieveData["setting"] == "partymode-toggle":
+ partyMode = not(partyMode)
+ return "200"
+ else:
+ return "400"
+@app.route("/search", methods=['POST'])
+def searchSongDB():
+ recieveData=request.get_json(force=True)
+ # the way i put the data in a list was really dumb looking back, i could and should have used a list of dictioaries like i was before
+ # i might try to change it but this layout is embedded deep in the client
+ tempData = {}
+ for i in songDatabaseList:
+ if ((i["title"].lower().find(recieveData['search'].lower())) > -1) or (recieveData['search'] == ""):
+ tempData[i["title"]] = [i["artist"],i["art"],i["file"]]
+ try:
+ if (i["artist"].lower().find(recieveData['search'].lower()) > -1):
+ tempData[i["title"]] = [i["artist"],i["art"],i["file"]]
+ except:
+ pass
+
+ return tempData
+
+@app.route("/songadd", methods=["POST"])
+def songadd():
+ recieveData=request.get_json(force=True)
+ queueSong(recieveData['song'])
+ return "200"
+@app.route("/playlist", methods=["POST"])
+def getPlaylist():
+ global songNext
+ tempPlaylist = []
+ for k in songDatabaseList:
+ if k["file"] == songNext:
+ temp = k.copy()
+ temp["playing"] = True
+ temp["time"] = player.get_time()/1000
+ tempPlaylist.append(temp)
+ for i in playlist:
+ for j in songDatabaseList:
+ if j["file"] == i:
+ tempPlaylist.append(j)
+ return tempPlaylist
+
+if __name__ == "__main__":
+ app.run(host='0.0.0.0', port='19054')
+
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 3aff8ca..a571016 100644
--- a/readme.md
+++ b/readme.md
@@ -3,7 +3,7 @@
## Purpose
The **Party Jukebox** is a program that allows many people to add music, skip songs, play, and pause from any web device to the same device and playlist. \
This was created for a personal use case for parties, and is a simple, (mostly) functional solution to have a collective playlist for local mp3 files. \
-Main strenghts compared to doing something similar using Spotify are that you can limit the songs that can be played to your selection. Songs can be chosen, but only from a list
+The main advantage compared to doing something similar using Spotify is that you can limit the songs that can be played to your selection. Songs can be chosen, but only from a list.
## Basic Setup
### Client Setup:
The client is a web application that can be hosted on any server, it need not be the same device running the music player.
@@ -11,7 +11,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:
```
sound/
@@ -27,6 +27,8 @@ webbyBits.py
4. Run `webbyBits.py`
You can now connect with the client and use the app as normal. \
+*Make sure you have turned down/off any other apps that might make noise or notification sounds* \
+\
Read on for specific information on each piece of the app.
## Details
These are specific details on each section of the app, and how to use them
@@ -38,6 +40,7 @@ These are specific details on each section of the app, and how to use them
- Art is retrieved from LastFM
- Running with `--mode (update/new)` either updates the current database and only adds new songs, or recreates the entire database (update is default)
- Running with `--art (True/False)` retrieves art from LastFM or doesn't (True is default)
+ - *Can only generate one song / 0.25 seconds, to avoid pinging the LastFM server too much*
- `songDatabase.json` stores all the information about each song in this format:
```
[
@@ -53,11 +56,13 @@ These are specific details on each section of the app, and how to use them
- `webbyBits.py` imports the database, runs all music playing, and accepts all commands from clients
- Searches return matching songs
- Accepts Play-Pause and Skip commands
+ - Uses port 19054 by default
### Client:
- \
+ \
From left to right:
- The playlist button shows the current queue of songs
+ - The currently playing song is identified, and has the duration listed
- The play-pause button toggles playing
- The skip button goes to the next track
- The search button opens the search screen (pictured)
@@ -65,6 +70,5 @@ From left to right:
- Server IP allows you to change the ip that the site connects to
- Alert time changes how long error/confirmation messages are shown for (Default 2s)
- Party Mode adds new songs to the queue when the queue has only 1 song in it
- - Volume controls the vlc volume of the connected server
-
-
+ - 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*
\ No newline at end of file