From 808bef5f5d0e5d17ac8428eb205e8f5ac57c5aab Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 30 Oct 2020 22:00:30 +0000 Subject: [PATCH 01/17] Add LoggingManager --- helpers/__init__.py | 0 helpers/logging_manager.py | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 helpers/__init__.py create mode 100644 helpers/logging_manager.py diff --git a/helpers/__init__.py b/helpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/helpers/logging_manager.py b/helpers/logging_manager.py new file mode 100644 index 0000000..b3c60e6 --- /dev/null +++ b/helpers/logging_manager.py @@ -0,0 +1,25 @@ +import logging +from helpers.os_environment import resolve_external_file_path + + +class LoggingManager(): + + logger = None + + def __init__(self, name): + self.logger = logging.getLogger(name) + + logging.basicConfig( + filename=resolve_external_file_path("/logs/" + name + ".txt"), + format='%(asctime)s | %(levelname)s | %(message)s', + level=logging.INFO, + filemode='a' + ) + self.logger.info("Logger Started.") + + def __del__(self): + logging.shutdown() + + @property + def log(self): + return self.logger From 7e2a974e5cfe3884e73c18a1a67bd9f9efee202b Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 30 Oct 2020 22:06:03 +0000 Subject: [PATCH 02/17] Move StateManager to /helpers and start adding player logging. --- state_manager.py => helpers/state_manager.py | 0 player.py | 27 ++++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) rename state_manager.py => helpers/state_manager.py (100%) diff --git a/state_manager.py b/helpers/state_manager.py similarity index 100% rename from state_manager.py rename to helpers/state_manager.py diff --git a/player.py b/player.py index 3f84325..1be40bf 100644 --- a/player.py +++ b/player.py @@ -11,10 +11,9 @@ import copy import json import time from pygame import mixer -from state_manager import StateManager +from helpers.state_manager import StateManager +from helpers.logging_manager import LoggingManager from mutagen.mp3 import MP3 -import os -os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" class Player(): @@ -22,6 +21,7 @@ class Player(): running = False out_q = None last_msg = None + logger = None __default_state = { "initialised": False, @@ -231,37 +231,38 @@ class Player(): self.out_q.put(response) def __init__(self, channel, in_q, out_q): - self.running = True - self.out_q = out_q setproctitle.setproctitle("BAPSicle - Player " + str(channel)) + self.running = True + self.out_q = out_q self.state = StateManager("channel" + str(channel), self.__default_state) - self.state.update("channel", channel) + self.logger = LoggingManager("channel" + str(channel)) + loaded_state = copy.copy(self.state.state) if loaded_state["output"]: - print("Setting output to: " + loaded_state["output"]) + self.logger.log.info("Setting output to: " + loaded_state["output"]) self.output(loaded_state["output"]) else: - print("Using default output device.") + self.logger.log.info("Using default output device.") self.output() if loaded_state["filename"]: - print("Loading filename: " + loaded_state["filename"]) + self.logger.log.info("Loading filename: " + loaded_state["filename"]) self.load(loaded_state["filename"]) if loaded_state["pos_true"] != 0: - print("Seeking to pos_true: " + str(loaded_state["pos_true"])) + self.logger.log.info("Seeking to pos_true: " + str(loaded_state["pos_true"])) self.seek(loaded_state["pos_true"]) if loaded_state["playing"] == True: - print("Resuming.") + self.logger.log.info("Resuming.") self.unpause() else: - print("No file was previously loaded.") + self.logger.log.info("No file was previously loaded.") while self.running: time.sleep(0.1) @@ -334,7 +335,7 @@ class Player(): except: raise - print("Quiting player ", channel) + self.logger.log.info("Quiting player ", channel) self.quit() self._retMsg("EXIT") From f29be9e38f3d5c064ce08725bcd6cc7c28260b9f Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 30 Oct 2020 22:15:51 +0000 Subject: [PATCH 03/17] Disable imports reordering & stop Pygame saying hello. --- dev/pre-commit | 3 ++- player.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/dev/pre-commit b/dev/pre-commit index c23b75c..3e5a081 100644 --- a/dev/pre-commit +++ b/dev/pre-commit @@ -9,9 +9,10 @@ import subprocess import sys import tempfile +#See for codes https://pypi.org/project/autopep8/ #features # don't fill in both of these select_codes = [] -ignore_codes = [] #"E121", "E122", "E123", "E124", "E125", "E126", "E127", "E128", "E129", "E131", "E501"] +ignore_codes = ["E402","E226","E24","W50","W690"] #"E121", "E122", "E123", "E124", "E125", "E126", "E127", "E128", "E129", "E131", "E501"] # Add things like "--max-line-length=120" below overrides = ["--max-line-length=120"] diff --git a/player.py b/player.py index 1be40bf..0d4f2d1 100644 --- a/player.py +++ b/player.py @@ -10,7 +10,12 @@ import setproctitle import copy import json import time + +# Stop the Pygame Hello message. +import os +os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" from pygame import mixer + from helpers.state_manager import StateManager from helpers.logging_manager import LoggingManager from mutagen.mp3 import MP3 From 536bc25b6ef56291546238f6f0a1652451d5530d Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 30 Oct 2020 22:19:38 +0000 Subject: [PATCH 04/17] gitignore audio and logs --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index 1bbdfbd..1411239 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,11 @@ build/build-exe-pyinstaller-command.bat build/build/BAPSicle/ build/output/ + +logs/ + +*.mp3 + +*.oga + +*.ogg From 331649e85cf3b0cc13c1bf7d9a297c306e0739bb Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 30 Oct 2020 23:14:29 +0000 Subject: [PATCH 05/17] Give players and server process titles (in theory) --- player.py | 4 +++- server.py | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/player.py b/player.py index 0d4f2d1..bdd0751 100644 --- a/player.py +++ b/player.py @@ -237,7 +237,9 @@ class Player(): def __init__(self, channel, in_q, out_q): - setproctitle.setproctitle("BAPSicle - Player " + str(channel)) + process_title = "Player: Channel " + str(channel) + setproctitle.setproctitle(process_title) + multiprocessing.current_process().name = process_title self.running = True self.out_q = out_q diff --git a/server.py b/server.py index f35c1e4..f0361af 100644 --- a/server.py +++ b/server.py @@ -9,7 +9,13 @@ setproctitle.setproctitle("BAPSicle - Server") class BAPSicleServer(): + def __init__(self): + + process_title = "Server" + setproctitle.setproctitle(process_title) + multiprocessing.current_process().name = process_title + startServer() def __del__(self): From 367e5d7980d479e5d21657cde2df3c0b0724da1c Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Fri, 30 Oct 2020 23:59:58 +0000 Subject: [PATCH 06/17] Add Logging to StateManager --- helpers/logging_manager.py | 3 ++- helpers/state_manager.py | 50 +++++++++++++++++++++++--------------- player.py | 5 ++-- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/helpers/logging_manager.py b/helpers/logging_manager.py index b3c60e6..4218ef4 100644 --- a/helpers/logging_manager.py +++ b/helpers/logging_manager.py @@ -15,9 +15,10 @@ class LoggingManager(): level=logging.INFO, filemode='a' ) - self.logger.info("Logger Started.") + self.logger.info("** LOGGER STARTED **") def __del__(self): + self.logger.info("** LOGGER EXITING **") logging.shutdown() @property diff --git a/helpers/state_manager.py b/helpers/state_manager.py index e81cffd..47685b6 100644 --- a/helpers/state_manager.py +++ b/helpers/state_manager.py @@ -1,38 +1,42 @@ +from helpers.logging_manager import LoggingManager import json import os +import logging from helpers.os_environment import resolve_external_file_path class StateManager: filepath = None + logger = None __state = {} - def __init__(self, name, default_state=None): + def __init__(self, name, logger: LoggingManager, default_state=None): + self.logger = logger + self.filepath = resolve_external_file_path("/state/" + name + ".json") + self._log("State file path set to: " + self.filepath) + if not os.path.isfile(self.filepath): - self.log("No file found for " + self.filepath) + self._log("No existing state file found.") try: # Try creating the file. open(self.filepath, "x") except: - self.log("failed to create state file") + self._log("Failed to create state file.", logging.CRITICAL) return - self.log("Saving state to " + self.filepath) + with open(self.filepath, 'r') as file: + file_state = file.read() - file = open(self.filepath, 'r') - - file_state = file.read() - file.close() - - # TODO: also check for invalid JSON state if file_state == "": - print("file empty") - + self._log("State file is empty. Setting default state.") self.state = default_state - else: - self.__state = json.loads(file_state) + try: + self.__state = json.loads(file_state) + except: + self._logException("Failed to parse state JSON. Resetting to default state.") + self.state = default_state @property def state(self): @@ -42,16 +46,22 @@ class StateManager: def state(self, state): self.__state = state - file = open(self.filepath, "w") + try: + state_json = json.dumps(state, indent=2, sort_keys=True) + except: + self._logException("Failed to dump JSON state.") + else: + with open(self.filepath, "w") as file: - file.write(json.dumps(state, indent=2, sort_keys=True)) - - file.close() + file.write(state_json) def update(self, key, value): state = self.state state[key] = value self.state = state - def log(self, msg): - print(msg) + def _log(self, text, level=logging.INFO): + self.logger.log.log(level, "State Manager: " + text) + + def _logException(self, text): + self.logger.log.exception("State Manager: " + text) diff --git a/player.py b/player.py index bdd0751..cfd9a9d 100644 --- a/player.py +++ b/player.py @@ -243,11 +243,12 @@ class Player(): self.running = True self.out_q = out_q - self.state = StateManager("channel" + str(channel), self.__default_state) - self.state.update("channel", channel) self.logger = LoggingManager("channel" + str(channel)) + self.state = StateManager("channel" + str(channel), self.logger, self.__default_state) + self.state.update("channel", channel) + loaded_state = copy.copy(self.state.state) if loaded_state["output"]: From 1d7ccaee127959f1a2d30e0ccc5f8676a8d44e6f Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sun, 1 Nov 2020 03:15:51 +0000 Subject: [PATCH 07/17] Reload track after output swap for convenience --- player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.py b/player.py index c139d1a..b2932ea 100644 --- a/player.py +++ b/player.py @@ -196,7 +196,6 @@ class Player(): def output(self, name=None): self.quit() self.state.update("output", name) - self.state.update("filename", "") try: if name: mixer.init(44100, -16, 1, 1024, devicename=name) @@ -205,6 +204,7 @@ class Player(): except: return False + self.load(self.state.state["filename"]) return True def _updateState(self, pos=None): From 435021536b84ce79eb36bf83f27579e93523cdd9 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sun, 1 Nov 2020 03:19:21 +0000 Subject: [PATCH 08/17] Resume playing after output switch. --- player.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/player.py b/player.py index b2932ea..82adb54 100644 --- a/player.py +++ b/player.py @@ -194,6 +194,7 @@ class Player(): self.state.update("paused", False) def output(self, name=None): + wasPlaying = self.state.state["playing"] self.quit() self.state.update("output", name) try: @@ -205,6 +206,10 @@ class Player(): return False self.load(self.state.state["filename"]) + + if wasPlaying: + self.unpause() + return True def _updateState(self, pos=None): From ee6084423d3a8bc9610dd86a50ca4456c66b14df Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Sun, 1 Nov 2020 03:27:26 +0000 Subject: [PATCH 09/17] Woops, it's been mono all this time. --- player.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/player.py b/player.py index 82adb54..ca1d814 100644 --- a/player.py +++ b/player.py @@ -199,9 +199,9 @@ class Player(): self.state.update("output", name) try: if name: - mixer.init(44100, -16, 1, 1024, devicename=name) + mixer.init(44100, -16, 2, 1024, devicename=name) else: - mixer.init(44100, -16, 1, 1024) + mixer.init(44100, -16, 2, 1024) except: return False From 2ec073392e3356dd18fec142e62a40030329d845 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 3 Nov 2020 22:48:11 +0000 Subject: [PATCH 10/17] Add more player logging. --- player.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/player.py b/player.py index ca1d814..69d4d41 100644 --- a/player.py +++ b/player.py @@ -80,6 +80,7 @@ class Player(): try: mixer.music.set_volume(1) except: + self.logger.log.exception("Failed to reset volume after attempting loaded test.") pass return False if position > 0: @@ -95,32 +96,31 @@ class Player(): return res def play(self, pos=0): - # if not self.isPlaying: try: mixer.music.play(0, pos) self.state.update("pos_offset", pos) except: + self.logger.log.exception("Failed to play at pos: " + str(pos)) return False self.state.update("paused", False) return True - # return False def pause(self): - # if self.isPlaying: - try: mixer.music.pause() except: + self.logger.log.exception("Failed to pause.") return False self.state.update("paused", True) return True - # return False def unpause(self): if not self.isPlaying: + position = self.state.state["pos_true"] try: - self.play(self.state.state["pos_true"]) + self.play(position) except: + self.logger.log.exception("Failed to unpause from pos: " + str(position)) return False self.state.update("paused", False) return True @@ -131,6 +131,7 @@ class Player(): try: mixer.music.stop() except: + self.logger.log.exception("Failed to stop playing.") return False self.state.update("pos", 0) self.state.update("pos_offset", 0) @@ -144,6 +145,7 @@ class Player(): try: self.play(pos) except: + self.logger.log.exception("Failed to seek to pos: " + str(pos)) return False return True else: @@ -163,10 +165,11 @@ class Player(): self.state.update("filename", filename) try: + self.logger.log.info("Loading file: " + str(filename)) mixer.music.load(filename) except: # We couldn't load that file. - print("Couldn't load file:", filename) + self.logger.log.exception("Couldn't load file: " + str(filename)) return False try: @@ -176,6 +179,7 @@ class Player(): else: self.state.update("length", mixer.Sound(filename).get_length()/1000) except: + self.logger.log.exception("Failed to update the length of item.") return False return True @@ -186,12 +190,16 @@ class Player(): self.state.update("paused", False) self.state.update("filename", "") except: + self.logger.log.exception("Failed to unload channel.") return False return not self.isLoaded def quit(self): - mixer.quit() - self.state.update("paused", False) + try: + mixer.quit() + self.state.update("paused", False) + except: + self.logger.log.exception("Failed to quit mixer.") def output(self, name=None): wasPlaying = self.state.state["playing"] @@ -203,6 +211,7 @@ class Player(): else: mixer.init(44100, -16, 2, 1024) except: + self.logger.log.exception("Failed to init mixer with device name: " + str(name)) return False self.load(self.state.state["filename"]) @@ -215,8 +224,6 @@ class Player(): def _updateState(self, pos=None): self.state.update("initialised", self.isInit) if self.isInit: - # TODO: get_pos returns the time since the player started playing - # This is NOT the same as the position through the song. if self.isPlaying: # Get one last update in, incase we're about to pause/stop it. self.state.update("pos", max(0, mixer.music.get_pos()/1000)) @@ -346,11 +353,14 @@ class Player(): # Catch the player being killed externally. except KeyboardInterrupt: + self.logger.log.info("Received KeyboardInterupt") break except SystemExit: + self.logger.log.info("Received SystemExit") break except: - raise + self.logger.log.exception("Received unexpected exception.") + break self.logger.log.info("Quiting player ", channel) self.quit() From e191595768f57447581314a3035e509c24e83e8e Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Tue, 3 Nov 2020 23:12:57 +0000 Subject: [PATCH 11/17] Log as .log instead. --- helpers/logging_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/logging_manager.py b/helpers/logging_manager.py index 4218ef4..8df6d65 100644 --- a/helpers/logging_manager.py +++ b/helpers/logging_manager.py @@ -10,7 +10,7 @@ class LoggingManager(): self.logger = logging.getLogger(name) logging.basicConfig( - filename=resolve_external_file_path("/logs/" + name + ".txt"), + filename=resolve_external_file_path("/logs/" + name + ".log"), format='%(asctime)s | %(levelname)s | %(message)s', level=logging.INFO, filemode='a' From bddab0208d6294527b1eb90dd2cbd31984c418f1 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Wed, 4 Nov 2020 01:19:56 +0000 Subject: [PATCH 12/17] Implement rate limiting to stop spamming state file writes --- helpers/state_manager.py | 68 +++++++++++++++++++++++++++++++++++----- player.py | 17 +++++++--- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/helpers/state_manager.py b/helpers/state_manager.py index 47685b6..dbe9052 100644 --- a/helpers/state_manager.py +++ b/helpers/state_manager.py @@ -2,6 +2,9 @@ from helpers.logging_manager import LoggingManager import json import os import logging +import time +from datetime import datetime +from copy import copy from helpers.os_environment import resolve_external_file_path @@ -9,8 +12,13 @@ class StateManager: filepath = None logger = None __state = {} + __state_in_file = {} + # Dict of times that params can be updated after, if the time is before current time, it can be written immediately. + __rate_limit_params_until = {} + __rate_limit_period_s = 0 - def __init__(self, name, logger: LoggingManager, default_state=None): + + def __init__(self, name, logger: LoggingManager, default_state=None, rate_limit_params=[], rate_limit_period_s = 5): self.logger = logger self.filepath = resolve_external_file_path("/state/" + name + ".json") @@ -30,7 +38,8 @@ class StateManager: if file_state == "": self._log("State file is empty. Setting default state.") - self.state = default_state + self.state = copy(default_state) + self.__state_in_file = copy(self.state) else: try: self.__state = json.loads(file_state) @@ -38,14 +47,35 @@ class StateManager: self._logException("Failed to parse state JSON. Resetting to default state.") self.state = default_state + # Now setup the rate limiting + # Essentially rate limit all values to "now" to start with, allowing the first update + # of all vars to succeed. + for param in rate_limit_params: + self.__rate_limit_params_until[param] = self._currentTimeS + self.__rate_limit_period_s = rate_limit_period_s + @property def state(self): - return self.__state + return copy(self.__state) @state.setter def state(self, state): - self.__state = state + self.__state = copy(state) + def write_to_file(self,state): + if self.__state_in_file == state: + # No change to be updated. + return + + self.__state_in_file = state + + # Make sure we're not manipulating state + state = copy(state) + + now = datetime.now() + + current_time = now.strftime("%H:%M:%S") + state["last_updated"] = current_time try: state_json = json.dumps(state, indent=2, sort_keys=True) except: @@ -56,12 +86,36 @@ class StateManager: file.write(state_json) def update(self, key, value): - state = self.state - state[key] = value - self.state = state + update_file = True + if (key in self.__rate_limit_params_until.keys()): + # The key we're trying to update is expected to be updating very often, + # We're therefore going to check before saving it. + if self.__rate_limit_params_until[key] > self._currentTimeS: + update_file = False + else: + self.__rate_limit_params_until[key] = self._currentTimeS + self.__rate_limit_period_s + + + state_to_update = self.state + + if state_to_update[key] == value: + # We're trying to update the state with the same value. + # In this case, ignore the update + return + + state_to_update[key] = value + + self.state = state_to_update + + if (update_file == True): + self.write_to_file(state_to_update) def _log(self, text, level=logging.INFO): self.logger.log.log(level, "State Manager: " + text) def _logException(self, text): self.logger.log.exception("State Manager: " + text) + + @property + def _currentTimeS(self): + return time.time() diff --git a/player.py b/player.py index 69d4d41..631f6a8 100644 --- a/player.py +++ b/player.py @@ -43,6 +43,13 @@ class Player(): "output": None } + __rate_limited_params = [ + "pos", + "pos_offset", + "pos_true", + "remaining" + ] + @property def isInit(self): try: @@ -222,16 +229,18 @@ class Player(): return True def _updateState(self, pos=None): + self.state.update("initialised", self.isInit) if self.isInit: - if self.isPlaying: + if (pos): + self.state.update("pos", max(0, pos)) + elif self.isPlaying: # Get one last update in, incase we're about to pause/stop it. self.state.update("pos", max(0, mixer.music.get_pos()/1000)) self.state.update("playing", self.isPlaying) self.state.update("loaded", self.isLoaded) - if (pos): - self.state.update("pos", max(0, pos)) + self.state.update("pos_true", self.state.state["pos"] + self.state.state["pos_offset"]) @@ -262,7 +271,7 @@ class Player(): self.logger = LoggingManager("channel" + str(channel)) - self.state = StateManager("channel" + str(channel), self.logger, self.__default_state) + self.state = StateManager("channel" + str(channel), self.logger, self.__default_state, self.__rate_limited_params) self.state.update("channel", channel) loaded_state = copy.copy(self.state.state) From cb42eec7572b8ff0763fe8933b3a6774470a16bc Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 5 Nov 2020 18:58:18 +0000 Subject: [PATCH 13/17] Fix PlanObject import issue --- helpers/state_manager.py | 23 +++++++++++------------ player.py | 10 +++++----- server.py | 4 ++-- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/helpers/state_manager.py b/helpers/state_manager.py index fa8d7d8..40257b5 100644 --- a/helpers/state_manager.py +++ b/helpers/state_manager.py @@ -1,10 +1,12 @@ -from helpers.logging_manager import LoggingManager import json import os import logging import time from datetime import datetime from copy import copy + +from plan import PlanObject +from helpers.logging_manager import LoggingManager from helpers.os_environment import resolve_external_file_path @@ -38,25 +40,22 @@ class StateManager: if file_state == "": self._log("State file is empty. Setting default state.") - self.state = copy(default_state) + self.state = default_state self.__state_in_file = copy(self.state) else: try: - self.state = json.loads(file_state) + file_state = json.loads(file_state) # Turn from JSON -> PlanObject - self.update( - "loaded_item", - PlanObject(self.__state["loaded_item"]) if self.state["loaded_item"] else None - ) - self.update( - "show_plan", - [PlanObject(obj) for obj in self.state["show_plan"]] - ) + file_state["loaded_item"] = PlanObject(file_state["loaded_item"]) if file_state["loaded_item"] else None + file_state["show_plan"] = [PlanObject(obj) for obj in file_state["show_plan"]] + + # Now feed the loaded state into the initialised state manager. + self.state = file_state except: self._logException("Failed to parse state JSON. Resetting to default state.") - self.state = copy(default_state) + self.state = default_state self.__state_in_file = copy(self.state) # Now setup the rate limiting diff --git a/player.py b/player.py index cd0dbd8..90597b1 100644 --- a/player.py +++ b/player.py @@ -244,7 +244,7 @@ class Player(): self.unload() updated: bool = False - + for i in range(len(self.state.state["show_plan"])): if self.state.state["show_plan"][i].timeslotitemid == timeslotitemid: self.state.update("loaded_item", self.state.state["show_plan"][i]) @@ -335,7 +335,7 @@ class Player(): if self.state.state["remaining"] == 0 and self.state.state["loaded_item"]: # Track has ended print("Finished", self.state.state["loaded_item"].name) - + # Repeat 1 if self.state.state["repeat"] == "ONE": self.play() @@ -351,11 +351,11 @@ class Player(): # Repeat All elif self.state.state["repeat"] == "ALL": self.load(self.state.state["show_plan"][0].timeslotitemid) - + # Play on Load if self.state.state["play_on_load"]: self.play() - + def _retMsg(self, msg, okay_str=False): response = self.last_msg + ":" @@ -431,7 +431,7 @@ class Player(): message_types: Dict[str, Callable[any, bool]] = { # TODO Check Types "STATUS": lambda: self._retMsg(self.status, True), - + # Audio Playout "PLAY": lambda: self._retMsg(self.play()), "PAUSE": lambda: self._retMsg(self.pause()), diff --git a/server.py b/server.py index 2093ade..8309b36 100644 --- a/server.py +++ b/server.py @@ -201,7 +201,7 @@ def add_to_plan(channel: int): @app.route("/player//move//") def move_plan(channel: int, timeslotitemid: int, position: int): channel_to_q[channel].put("MOVE:" + json.dumps({"timeslotitemid": timeslotitemid, "position": position})) - + #TODO Return return True @@ -254,7 +254,7 @@ def all_stop(): @app.route("/player/all/clear") def clear_all_channels(): for channel in channel_to_q: - channel.put("CLEAR") + channel.put("CLEAR") return ui_status() From 23647de797f18bcb461302f959086bbe3e8b62df Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 5 Nov 2020 18:59:28 +0000 Subject: [PATCH 14/17] Re-add hot output switching, add default output support. --- player.py | 6 +++++- templates/config.html | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/player.py b/player.py index 90597b1..671280b 100644 --- a/player.py +++ b/player.py @@ -297,9 +297,10 @@ class Player(): def output(self, name=None): wasPlaying = self.state.state["playing"] + name = None if name == "none" else name + self.quit() self.state.update("output", name) - self.state.update("loaded_item", None) try: if name: mixer.init(44100, -16, 2, 1024, devicename=name) @@ -309,6 +310,9 @@ class Player(): self.logger.log.exception("Failed to init mixer with device name: " + str(name)) return False + loadedItem = self.state.state["loaded_item"] + if (loadedItem): + self.load(loadedItem.timeslotitemid) if wasPlaying: self.unpause() diff --git a/templates/config.html b/templates/config.html index 7f37597..33f5e8d 100644 --- a/templates/config.html +++ b/templates/config.html @@ -1,8 +1,9 @@ {% extends 'base.html' %} {% block content_inner %} + Set Channel 0 Set Channel 1 Set Channel 2 - System Default Output
{% for output in data.outputs %} Set Channel 0 Set Channel 1 Set Channel 2 - {{output.name}}
{% endfor %}
-{% endblock %} \ No newline at end of file +{% endblock %} From 1967432f11ee861b3e038462b0a2403adfcd9b02 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 5 Nov 2020 19:03:26 +0000 Subject: [PATCH 15/17] Add logs folder mkdir to installer --- install/install.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/install.bat b/install/install.bat index 504fb30..44691ab 100644 --- a/install/install.bat +++ b/install/install.bat @@ -5,7 +5,7 @@ set service_name="BAPSicle" mkdir %install_path% mkdir %install_path%\state - +mkdir %install_path%\logs cd %~dp0\nssm nssm stop %service_name% From 81eb1746df81d8e8619917a47a0aee4f0268b0e1 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 5 Nov 2020 19:04:07 +0000 Subject: [PATCH 16/17] Don't auto play for now on load, it does that itself. --- server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index 8309b36..b1c0e54 100644 --- a/server.py +++ b/server.py @@ -306,8 +306,8 @@ def startServer(): } channel_to_q[0].put("ADD:" + json.dumps(new_item)) - channel_to_q[0].put("LOAD:0") - channel_to_q[0].put("PLAY") + #channel_to_q[0].put("LOAD:0") + #channel_to_q[0].put("PLAY") # Don't use reloader, it causes Nested Processes! From 0a043988499aa639f5935055cd6afbb84a894356 Mon Sep 17 00:00:00 2001 From: Matthew Stratford Date: Thu, 5 Nov 2020 19:06:16 +0000 Subject: [PATCH 17/17] Fix import for standalone player --- player.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/player.py b/player.py index 671280b..54669d1 100644 --- a/player.py +++ b/player.py @@ -37,6 +37,7 @@ os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" from pygame import mixer from mutagen.mp3 import MP3 +from helpers.os_environment import isMacOS from helpers.state_manager import StateManager from helpers.logging_manager import LoggingManager @@ -498,7 +499,7 @@ def showOutput(in_q, out_q): if __name__ == "__main__": - if isMacOS: + if isMacOS(): multiprocessing.set_start_method("spawn", True) in_q = multiprocessing.Queue()