From db446ef9728813b9543926ab91d1bfee97bbb60d Mon Sep 17 00:00:00 2001 From: KubaPro010 Date: Sat, 11 Apr 2026 14:10:55 +0200 Subject: [PATCH] change web stuff, and add a skip counter instead of skip_next --- modules/__init__.py | 7 ++++-- modules/active_modifier.py | 19 ++++++++------- modules/advisor.py | 1 + modules/rds.py | 1 + modules/web.html | 49 +++++++++++++++++++++----------------- modules/web.py | 34 +++++++++++++++++++++++++- 6 files changed, 78 insertions(+), 33 deletions(-) diff --git a/modules/__init__.py b/modules/__init__.py index 8dffa57..54fad9f 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -159,11 +159,14 @@ class InterModuleCommunication: if name in self.names_modules.keys(): return False self.names_modules[name] = module 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 """ - 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) class PlaylistParser: diff --git a/modules/active_modifier.py b/modules/active_modifier.py index 1228791..a0cbd56 100644 --- a/modules/active_modifier.py +++ b/modules/active_modifier.py @@ -22,7 +22,7 @@ class Module(ActiveModifier): self.morning_start = self.day_end = 0 self.file_lock = Lock() 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]): self.playlist = playlist 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) next_track = track self.limit_tracks = False - if self.skip_next: - logger.info("Skip next flag was on, skipping this song.") - self.skip_next = False + if self.skip_next > 0: + logger.info("Skipping...") + self.skip_next -= 1 return self.play(index, track, next_track) return (self.last_track, next_track), True elif len(self.originals): @@ -124,9 +124,9 @@ class Module(ActiveModifier): logger.warning("Skipping track as it the next day") return (None, None), None 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.") - self.skip_next = False + self.skip_next -= 1 return (None, None), None return (self.last_track, next_track), False @@ -163,8 +163,11 @@ class Module(ActiveModifier): i += 1 with open(TOPLAY, "w") as f: f.write(first_line.strip() + "\n") return {"status": "ok", "data": [first_line.strip()]} - elif data.get("action") == "skip_next": - if data.get("set", True): self.skip_next = not self.skip_next + elif data.get("action") == "skip_next": return {"status": "removed"} + 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} activemod = Module() diff --git a/modules/advisor.py b/modules/advisor.py index ff26e99..39fc33b 100644 --- a/modules/advisor.py +++ b/modules/advisor.py @@ -104,6 +104,7 @@ class Module(PlaylistAdvisor): logger.info(f"Playing {current_day} night playlist...") self.last_mod_time = Time.get_playlist_modification_time(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 def new_playlist(self) -> bool: if self.custom_playlist and self.custom_playlist_path.exists(): diff --git a/modules/rds.py b/modules/rds.py index 9b43a95..d64253b 100644 --- a/modules/rds.py +++ b/modules/rds.py @@ -81,6 +81,7 @@ class Module(PlayerModule): def on_new_track(self, index: int, track: Track, next_track: Track | None): if track.official: 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.debug(f"{rds_rtp=}") diff --git a/modules/web.html b/modules/web.html index 2b06868..c675de1 100644 --- a/modules/web.html +++ b/modules/web.html @@ -150,7 +150,11 @@
- +
+ + Skip next: 0 + +
@@ -208,7 +212,8 @@
Keys: S skip - N skip next + N +skip + M -skip J jingle ENTER add file
@@ -226,7 +231,7 @@ let selectedSubFile = null; let basePath = ""; let subbasePath = ""; - let skipNext = false; + let skipCount = 0; // UI Toggle Function function toggleSection(id) { @@ -246,13 +251,13 @@ function connectWs(){ document.getElementById("server-status").textContent = "connecting..."; - ws = new WebSocket("ws://192.168.1.93:3001"); + ws = new WebSocket("/ws"); ws.addEventListener("open", () => { document.getElementById("server-status").textContent = "connected"; reconnectDelay = 1000; 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", () => { @@ -287,7 +292,7 @@ } else if(msg.event === "new_track"){ applyTrackState(msg.data); 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"){ applyProgressState(msg.data); } else if(msg.event === "toplay") { @@ -295,11 +300,9 @@ renderQueue(); } else if(msg.event === "request_dir") { applySubdir(msg.data || {}) - } else if(msg.event === "skip_next") { - skipNext = msg.data?.data === true; - const btn = document.getElementById("skpn-btn"); - if(!skipNext) btn.classList.remove("activated"); - else btn.classList.add("activated"); + } else if(msg.event === "skipc") { + skipCount = msg.data?.data ?? 0; + document.getElementById("skpn-count").textContent = skipCount; renderPlaylist(); renderQueue(); } @@ -396,8 +399,8 @@ li.classList.add("pointer"); 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 = li.textContent + (i === currentTrackIndex ? "▶ " : " ") + displayPath; ul.appendChild(li); @@ -416,7 +419,7 @@ Queue.forEach((element, i) => { const li = document.createElement("li"); li.textContent = element; - if(i === 0 && skipNext) li.style.textDecoration = "line-through"; + if(i < skipCount) li.style.textDecoration = "line-through"; ul.appendChild(li); }); updateControls() @@ -498,8 +501,11 @@ document.getElementById("skip-btn").addEventListener("click", () => { ws.send(JSON.stringify({action:"skip"})); }); - document.getElementById("skpn-btn").addEventListener("click", () => { - ws.send(JSON.stringify({action:"skip_next"})); + document.getElementById("skpn-inc").addEventListener("click", () => { + 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", () => { ws.send(JSON.stringify({action:"jingle"})); @@ -550,12 +556,11 @@ document.addEventListener("keydown", e => { if (e.target.tagName === "INPUT") return; - if (e.key === "Enter") { - if (addSelectedFileToQueue(e.shiftKey)) e.preventDefault(); - } - if (e.key === "s") ws.send(JSON.stringify({action:"skip"})); - if (e.key === "n") ws.send(JSON.stringify({action:"skip_next"})); - if (e.key.toLowerCase() === "j") ws.send(JSON.stringify({action:"jingle", top: e.shiftKey})); + if (e.key === "Enter" && 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})); + else if (e.key === "m") ws.send(JSON.stringify({action:"skipc", remove: -1})); + else if (e.key.toLowerCase() === "j") ws.send(JSON.stringify({action:"jingle", top: e.shiftKey})); }); // Start diff --git a/modules/web.py b/modules/web.py index 7687a8e..b73aa44 100644 --- a/modules/web.py +++ b/modules/web.py @@ -6,6 +6,8 @@ import asyncio import websockets from websockets import ServerConnection, Request, Response, Headers +from modules import InterModuleCommunication + from . import Track, PlayerModule, Path, BaseIMCModule 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, "") clients.discard(websocket) 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() return Response( 200, @@ -239,6 +241,10 @@ class Module(PlayerModule): 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}}) except Exception: pass + + def imc(self, imc: InterModuleCommunication) -> None: + self._imc = imc + imc.register(self, "web") def shutdown(self): self.ipc_thread_running = False @@ -261,3 +267,29 @@ class Module(PlayerModule): self.ws_process.join(timeout=1) 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 \ No newline at end of file