mirror of
https://github.com/radio95-rnt/RadioPlayer.git
synced 2026-07-29 15:29:14 +02:00
change web stuff, and add a skip counter instead of skip_next
This commit is contained in:
+5
-2
@@ -159,11 +159,14 @@ class InterModuleCommunication:
|
|||||||
if name in self.names_modules.keys(): return False
|
if name in self.names_modules.keys(): return False
|
||||||
self.names_modules[name] = module
|
self.names_modules[name] = module
|
||||||
return True
|
return True
|
||||||
def send(self, source: BaseIMCModule, name: str, data: object) -> object:
|
def send(self, source: BaseIMCModule, name: str, data: object, aggresive: bool = True) -> object:
|
||||||
"""
|
"""
|
||||||
Sends the data to a named module, and return its response
|
Sends the data to a named module, and return its response
|
||||||
"""
|
"""
|
||||||
if not name in self.names_modules.keys(): raise ModuleNotFoundError("No such module")
|
if not name in self.names_modules.keys():
|
||||||
|
if aggresive: raise ModuleNotFoundError("No such module")
|
||||||
|
return None
|
||||||
|
|
||||||
return self.names_modules[name].imc_data(source, next((k for k, v in self.names_modules.items() if v is source), None), data, False)
|
return self.names_modules[name].imc_data(source, next((k for k, v in self.names_modules.items() if v is source), None), data, False)
|
||||||
|
|
||||||
class PlaylistParser:
|
class PlaylistParser:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class Module(ActiveModifier):
|
|||||||
self.morning_start = self.day_end = 0
|
self.morning_start = self.day_end = 0
|
||||||
self.file_lock = Lock()
|
self.file_lock = Lock()
|
||||||
self.crossfade = DEFAULT_CROSSFADE
|
self.crossfade = DEFAULT_CROSSFADE
|
||||||
self.skip_next = False
|
self.skip_next = 0
|
||||||
def on_new_playlist(self, playlist: list[Track], global_args: dict[str, str]):
|
def on_new_playlist(self, playlist: list[Track], global_args: dict[str, str]):
|
||||||
self.playlist = playlist
|
self.playlist = playlist
|
||||||
self.originals = []
|
self.originals = []
|
||||||
@@ -95,9 +95,9 @@ class Module(ActiveModifier):
|
|||||||
self.last_track = Track(song, current_track_fade_out, current_track_fade_in, official, {}, focus_time_offset=-current_track_fade_out)
|
self.last_track = Track(song, current_track_fade_out, current_track_fade_in, official, {}, focus_time_offset=-current_track_fade_out)
|
||||||
next_track = track
|
next_track = track
|
||||||
self.limit_tracks = False
|
self.limit_tracks = False
|
||||||
if self.skip_next:
|
if self.skip_next > 0:
|
||||||
logger.info("Skip next flag was on, skipping this song.")
|
logger.info("Skipping...")
|
||||||
self.skip_next = False
|
self.skip_next -= 1
|
||||||
return self.play(index, track, next_track)
|
return self.play(index, track, next_track)
|
||||||
return (self.last_track, next_track), True
|
return (self.last_track, next_track), True
|
||||||
elif len(self.originals):
|
elif len(self.originals):
|
||||||
@@ -124,9 +124,9 @@ class Module(ActiveModifier):
|
|||||||
logger.warning("Skipping track as it the next day")
|
logger.warning("Skipping track as it the next day")
|
||||||
return (None, None), None
|
return (None, None), None
|
||||||
if last_track_duration: logger.info("Track ends at", repr(future))
|
if last_track_duration: logger.info("Track ends at", repr(future))
|
||||||
if self.skip_next:
|
if self.skip_next > 0:
|
||||||
logger.info("Skip next flag was on, skipping this song.")
|
logger.info("Skip next flag was on, skipping this song.")
|
||||||
self.skip_next = False
|
self.skip_next -= 1
|
||||||
return (None, None), None
|
return (None, None), None
|
||||||
return (self.last_track, next_track), False
|
return (self.last_track, next_track), False
|
||||||
|
|
||||||
@@ -163,8 +163,11 @@ class Module(ActiveModifier):
|
|||||||
i += 1
|
i += 1
|
||||||
with open(TOPLAY, "w") as f: f.write(first_line.strip() + "\n")
|
with open(TOPLAY, "w") as f: f.write(first_line.strip() + "\n")
|
||||||
return {"status": "ok", "data": [first_line.strip()]}
|
return {"status": "ok", "data": [first_line.strip()]}
|
||||||
elif data.get("action") == "skip_next":
|
elif data.get("action") == "skip_next": return {"status": "removed"}
|
||||||
if data.get("set", True): self.skip_next = not self.skip_next
|
elif data.get("action") == "skipc":
|
||||||
|
if (count := data.get("set", -1)) > -1: self.skip_next = count
|
||||||
|
if (count2 := data.get("add", -1)) > -1: self.skip_next += count2
|
||||||
|
if (count3 := data.get("remove", 1)) < -1: self.skip_next += count3
|
||||||
return {"status": "ok", "data": self.skip_next}
|
return {"status": "ok", "data": self.skip_next}
|
||||||
|
|
||||||
activemod = Module()
|
activemod = Module()
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class Module(PlaylistAdvisor):
|
|||||||
logger.info(f"Playing {current_day} night playlist...")
|
logger.info(f"Playing {current_day} night playlist...")
|
||||||
self.last_mod_time = Time.get_playlist_modification_time(night_playlist)
|
self.last_mod_time = Time.get_playlist_modification_time(night_playlist)
|
||||||
self.last_playlist = night_playlist
|
self.last_playlist = night_playlist
|
||||||
|
if self.class_imc: self.class_imc.send(self, "web", {"playlist": str(self.last_playlist)})
|
||||||
return self.last_playlist
|
return self.last_playlist
|
||||||
def new_playlist(self) -> bool:
|
def new_playlist(self) -> bool:
|
||||||
if self.custom_playlist and self.custom_playlist_path.exists():
|
if self.custom_playlist and self.custom_playlist_path.exists():
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ class Module(PlayerModule):
|
|||||||
def on_new_track(self, index: int, track: Track, next_track: Track | None):
|
def on_new_track(self, index: int, track: Track, next_track: Track | None):
|
||||||
if track.official:
|
if track.official:
|
||||||
rds_rt, rds_rtp = update_rds(track.path.name)
|
rds_rt, rds_rtp = update_rds(track.path.name)
|
||||||
|
self._imc.send(self, "web", {"rt": rds_rt, "rtp": rds_rtp}, False)
|
||||||
logger.info(f"RT set to '{rds_rt}'")
|
logger.info(f"RT set to '{rds_rt}'")
|
||||||
logger.debug(f"{rds_rtp=}")
|
logger.debug(f"{rds_rtp=}")
|
||||||
|
|
||||||
|
|||||||
+27
-22
@@ -150,7 +150,11 @@
|
|||||||
<div class="controls" style="margin-top:10px">
|
<div class="controls" style="margin-top:10px">
|
||||||
<button id="skip-btn" class="btn">⏭ Skip</button>
|
<button id="skip-btn" class="btn">⏭ Skip</button>
|
||||||
<button id="clear-btn" class="btn">✖ Clear Queue</button>
|
<button id="clear-btn" class="btn">✖ Clear Queue</button>
|
||||||
<button id="skpn-btn" class="btn">⏭? Skip Next</button>
|
<div style="display:flex;align-items:center;gap:4px;border:1px solid rgba(255,255,255,0.06);border-radius:8px;padding:2px 4px">
|
||||||
|
<button id="skpn-dec" class="btn" style="padding:4px 10px;border:none">−</button>
|
||||||
|
<span style="font-size:13px;min-width:60px;text-align:center">Skip next: <b id="skpn-count">0</b></span>
|
||||||
|
<button id="skpn-inc" class="btn" style="padding:4px 10px;border:none">+</button>
|
||||||
|
</div>
|
||||||
<button id="jingle-btn" class="btn">🕭 Jingle</button>
|
<button id="jingle-btn" class="btn">🕭 Jingle</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -208,7 +212,8 @@
|
|||||||
<div class="muted small" id="keybinds">
|
<div class="muted small" id="keybinds">
|
||||||
<b>Keys:</b>
|
<b>Keys:</b>
|
||||||
<span><kbd>S</kbd> skip</span>
|
<span><kbd>S</kbd> skip</span>
|
||||||
<span><kbd>N</kbd> skip next</span>
|
<span><kbd>N</kbd> +skip</span>
|
||||||
|
<span><kbd>M</kbd> -skip</span>
|
||||||
<span><kbd>J</kbd> jingle</span>
|
<span><kbd>J</kbd> jingle</span>
|
||||||
<span><kbd>ENTER</kbd> add file</span>
|
<span><kbd>ENTER</kbd> add file</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -226,7 +231,7 @@
|
|||||||
let selectedSubFile = null;
|
let selectedSubFile = null;
|
||||||
let basePath = "";
|
let basePath = "";
|
||||||
let subbasePath = "";
|
let subbasePath = "";
|
||||||
let skipNext = false;
|
let skipCount = 0;
|
||||||
|
|
||||||
// UI Toggle Function
|
// UI Toggle Function
|
||||||
function toggleSection(id) {
|
function toggleSection(id) {
|
||||||
@@ -246,13 +251,13 @@
|
|||||||
|
|
||||||
function connectWs(){
|
function connectWs(){
|
||||||
document.getElementById("server-status").textContent = "connecting...";
|
document.getElementById("server-status").textContent = "connecting...";
|
||||||
ws = new WebSocket("ws://192.168.1.93:3001");
|
ws = new WebSocket("/ws");
|
||||||
|
|
||||||
ws.addEventListener("open", () => {
|
ws.addEventListener("open", () => {
|
||||||
document.getElementById("server-status").textContent = "connected";
|
document.getElementById("server-status").textContent = "connected";
|
||||||
reconnectDelay = 1000;
|
reconnectDelay = 1000;
|
||||||
ws.send(JSON.stringify({action:"get_toplay"}));
|
ws.send(JSON.stringify({action:"get_toplay"}));
|
||||||
ws.send(JSON.stringify({action:"skip_next",set:false}));
|
ws.send(JSON.stringify({action:"skipc",set:0}));
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.addEventListener("close", () => {
|
ws.addEventListener("close", () => {
|
||||||
@@ -287,7 +292,7 @@
|
|||||||
} else if(msg.event === "new_track"){
|
} else if(msg.event === "new_track"){
|
||||||
applyTrackState(msg.data);
|
applyTrackState(msg.data);
|
||||||
ws.send(JSON.stringify({action:"get_toplay"}));
|
ws.send(JSON.stringify({action:"get_toplay"}));
|
||||||
ws.send(JSON.stringify({action:"skip_next",set:false}));
|
ws.send(JSON.stringify({action:"skipc",set:0}));
|
||||||
} else if(msg.event === "progress"){
|
} else if(msg.event === "progress"){
|
||||||
applyProgressState(msg.data);
|
applyProgressState(msg.data);
|
||||||
} else if(msg.event === "toplay") {
|
} else if(msg.event === "toplay") {
|
||||||
@@ -295,11 +300,9 @@
|
|||||||
renderQueue();
|
renderQueue();
|
||||||
} else if(msg.event === "request_dir") {
|
} else if(msg.event === "request_dir") {
|
||||||
applySubdir(msg.data || {})
|
applySubdir(msg.data || {})
|
||||||
} else if(msg.event === "skip_next") {
|
} else if(msg.event === "skipc") {
|
||||||
skipNext = msg.data?.data === true;
|
skipCount = msg.data?.data ?? 0;
|
||||||
const btn = document.getElementById("skpn-btn");
|
document.getElementById("skpn-count").textContent = skipCount;
|
||||||
if(!skipNext) btn.classList.remove("activated");
|
|
||||||
else btn.classList.add("activated");
|
|
||||||
renderPlaylist();
|
renderPlaylist();
|
||||||
renderQueue();
|
renderQueue();
|
||||||
}
|
}
|
||||||
@@ -396,8 +399,8 @@
|
|||||||
li.classList.add("pointer");
|
li.classList.add("pointer");
|
||||||
currentIndex = i - 1;
|
currentIndex = i - 1;
|
||||||
}
|
}
|
||||||
if(currentIndex !== null && (currentIndex+1 === i) && skipNext && Queue.length === 0) li.style.textDecoration = "line-through";
|
if(currentIndex !== null && Queue.length === 0 && i > currentIndex && i <= currentIndex + skipCount)
|
||||||
|
li.style.textDecoration = "line-through";
|
||||||
li.textContent = ` ${String(idx).padStart(2,'0')}: `;
|
li.textContent = ` ${String(idx).padStart(2,'0')}: `;
|
||||||
li.textContent = li.textContent + (i === currentTrackIndex ? "▶ " : " ") + displayPath;
|
li.textContent = li.textContent + (i === currentTrackIndex ? "▶ " : " ") + displayPath;
|
||||||
ul.appendChild(li);
|
ul.appendChild(li);
|
||||||
@@ -416,7 +419,7 @@
|
|||||||
Queue.forEach((element, i) => {
|
Queue.forEach((element, i) => {
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
li.textContent = element;
|
li.textContent = element;
|
||||||
if(i === 0 && skipNext) li.style.textDecoration = "line-through";
|
if(i < skipCount) li.style.textDecoration = "line-through";
|
||||||
ul.appendChild(li);
|
ul.appendChild(li);
|
||||||
});
|
});
|
||||||
updateControls()
|
updateControls()
|
||||||
@@ -498,8 +501,11 @@
|
|||||||
document.getElementById("skip-btn").addEventListener("click", () => {
|
document.getElementById("skip-btn").addEventListener("click", () => {
|
||||||
ws.send(JSON.stringify({action:"skip"}));
|
ws.send(JSON.stringify({action:"skip"}));
|
||||||
});
|
});
|
||||||
document.getElementById("skpn-btn").addEventListener("click", () => {
|
document.getElementById("skpn-inc").addEventListener("click", () => {
|
||||||
ws.send(JSON.stringify({action:"skip_next"}));
|
ws.send(JSON.stringify({action:"skipc", add: 1}));
|
||||||
|
});
|
||||||
|
document.getElementById("skpn-dec").addEventListener("click", () => {
|
||||||
|
ws.send(JSON.stringify({action:"skipc", remove: -1}));
|
||||||
});
|
});
|
||||||
document.getElementById("jingle-btn").addEventListener("click", () => {
|
document.getElementById("jingle-btn").addEventListener("click", () => {
|
||||||
ws.send(JSON.stringify({action:"jingle"}));
|
ws.send(JSON.stringify({action:"jingle"}));
|
||||||
@@ -550,12 +556,11 @@
|
|||||||
|
|
||||||
document.addEventListener("keydown", e => {
|
document.addEventListener("keydown", e => {
|
||||||
if (e.target.tagName === "INPUT") return;
|
if (e.target.tagName === "INPUT") return;
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter" && addSelectedFileToQueue(e.shiftKey)) e.preventDefault();
|
||||||
if (addSelectedFileToQueue(e.shiftKey)) e.preventDefault();
|
else if (e.key === "s") ws.send(JSON.stringify({action:"skip"}));
|
||||||
}
|
else if (e.key === "n") ws.send(JSON.stringify({action:"skipc", add: 1}));
|
||||||
if (e.key === "s") ws.send(JSON.stringify({action:"skip"}));
|
else if (e.key === "m") ws.send(JSON.stringify({action:"skipc", remove: -1}));
|
||||||
if (e.key === "n") ws.send(JSON.stringify({action:"skip_next"}));
|
else if (e.key.toLowerCase() === "j") ws.send(JSON.stringify({action:"jingle", top: e.shiftKey}));
|
||||||
if (e.key.toLowerCase() === "j") ws.send(JSON.stringify({action:"jingle", top: e.shiftKey}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start
|
// Start
|
||||||
|
|||||||
+33
-1
@@ -6,6 +6,8 @@ import asyncio
|
|||||||
import websockets
|
import websockets
|
||||||
from websockets import ServerConnection, Request, Response, Headers
|
from websockets import ServerConnection, Request, Response, Headers
|
||||||
|
|
||||||
|
from modules import InterModuleCommunication
|
||||||
|
|
||||||
from . import Track, PlayerModule, Path, BaseIMCModule
|
from . import Track, PlayerModule, Path, BaseIMCModule
|
||||||
|
|
||||||
MAIN_PATH_DIR = Path("/home/user/mixes")
|
MAIN_PATH_DIR = Path("/home/user/mixes")
|
||||||
@@ -141,7 +143,7 @@ def websocket_server_process(shared_data: dict, imc_q: multiprocessing.Queue, ws
|
|||||||
await websocket.close(1001, "")
|
await websocket.close(1001, "")
|
||||||
clients.discard(websocket)
|
clients.discard(websocket)
|
||||||
async def process_request(websocket: ServerConnection, request: Request):
|
async def process_request(websocket: ServerConnection, request: Request):
|
||||||
if request.path == "/web.html" and (file := Path(__file__, "..", "web.html").resolve()).exists():
|
if request.path == "/" and (file := Path(__file__, "..", "web.html").resolve()).exists():
|
||||||
data = file.read_bytes()
|
data = file.read_bytes()
|
||||||
return Response(
|
return Response(
|
||||||
200,
|
200,
|
||||||
@@ -239,6 +241,10 @@ class Module(PlayerModule):
|
|||||||
def imc_data(self, source: BaseIMCModule, source_name: str | None, data: object, broadcast: bool) -> object:
|
def imc_data(self, source: BaseIMCModule, source_name: str | None, data: object, broadcast: bool) -> object:
|
||||||
try: self.ws_q.put({"event": "imc", "data": {"name": source_name, "data": data, "broadcast": broadcast}})
|
try: self.ws_q.put({"event": "imc", "data": {"name": source_name, "data": data, "broadcast": broadcast}})
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
|
def imc(self, imc: InterModuleCommunication) -> None:
|
||||||
|
self._imc = imc
|
||||||
|
imc.register(self, "web")
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
self.ipc_thread_running = False
|
self.ipc_thread_running = False
|
||||||
@@ -261,3 +267,29 @@ class Module(PlayerModule):
|
|||||||
self.ws_process.join(timeout=1)
|
self.ws_process.join(timeout=1)
|
||||||
|
|
||||||
module = Module()
|
module = Module()
|
||||||
|
|
||||||
|
|
||||||
|
# This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
# distribute this software, either in source code form or as a compiled
|
||||||
|
# binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
# means.
|
||||||
|
|
||||||
|
# In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
# of this software dedicate any and all copyright interest in the
|
||||||
|
# software to the public domain. We make this dedication for the benefit
|
||||||
|
# of the public at large and to the detriment of our heirs and
|
||||||
|
# successors. We intend this dedication to be an overt act of
|
||||||
|
# relinquishment in perpetuity of all present and future rights to this
|
||||||
|
# software under copyright law.
|
||||||
|
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
# OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
# For more information, please refer to <https://unlicense.org>
|
||||||
Reference in New Issue
Block a user