From 08ed7c25f5f0a9fe0b3556a24d8bb206bd7c8329 Mon Sep 17 00:00:00 2001 From: Chris Pastl <chris@crispybits.app> Date: Wed, 15 May 2024 00:19:50 +0200 Subject: [PATCH] refactor: add timeout to Channel.load() --- src/aura_engine/core/channels.py | 22 +++++++++++++--------- src/aura_engine/engine.py | 2 +- src/aura_engine/scheduling/domain.py | 1 + src/aura_engine/scheduling/scheduler.py | 15 +++++++-------- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/aura_engine/core/channels.py b/src/aura_engine/core/channels.py index 469475ea..ee087b1c 100644 --- a/src/aura_engine/core/channels.py +++ b/src/aura_engine/core/channels.py @@ -213,7 +213,7 @@ class GenericChannel: self.volume = volume self.remaining = remain - def load(self, metadata: dict = None) -> bool: + def load(self, timeout: int = None, metadata: dict = None) -> bool: """ Interface definition for loading a channel track. @@ -330,7 +330,7 @@ class QueueChannel(GenericChannel): self.type = ChannelType.QUEUE super().__init__(channel_index, channel_name, mixer) - def load(self, uri: str = None, metadata: dict = None): + def load(self, uri: str = None, timeout: int = None, metadata: dict = None): """ Load the provided URI and pass metadata. @@ -432,7 +432,7 @@ class StreamChannel(GenericChannel): self.type = ChannelType.HTTP super().__init__(channel_index, channel_name, mixer) - def load(self, uri: str = None, metadata: dict = None): + def load(self, uri: str = None, timeout: int = None, metadata: dict = None): """ Load the given stream item and updates the playlist items's status codes. @@ -444,18 +444,22 @@ class StreamChannel(GenericChannel): (bool): True if track loaded successfully """ + if timeout is None: + timeout = 0 + self.logger.debug(SU.pink(f"Loading stream '{uri}'")) - retry_delay = self.config.scheduler.input_stream.retry_delay - max_retries = self.config.scheduler.input_stream.max_retries - retries = 0 self.stop() self.set_url(uri) self.start() + retry_until = SU.timestamp() + timeout + retry_delay = self.config.scheduler.input_stream.retry_delay + retries = 0 + while not self.is_ready(uri): - if retries >= max_retries: - msg = f"Stream connection failed after {retries * retry_delay} seconds!" + if SU.timestamp() > retry_until: + msg = f"Stream connection failed after {retries} retries in {timeout} seconds" raise LoadSourceException(msg) time.sleep(retry_delay) retries += 1 @@ -552,7 +556,7 @@ class LineChannel(GenericChannel): self.type = ChannelType.LIVE super().__init__(channel_index, channel_name, mixer) - def load(self, uri: str = None, metadata: dict = None): + def load(self, uri: str = None, timeout: int = None, metadata: dict = None): """ Load the line channel. diff --git a/src/aura_engine/engine.py b/src/aura_engine/engine.py index 94906cc6..f10ef3a2 100644 --- a/src/aura_engine/engine.py +++ b/src/aura_engine/engine.py @@ -329,7 +329,7 @@ class Player: item.play.set_loading(chosen_channel) self.logger.info(SU.pink(msg)) - is_ready = item.play.channel.load(uri, metadata=metadata) + is_ready = item.play.channel.load(uri, timeout=item.duration, metadata=metadata) if is_ready: item.play.set_ready() diff --git a/src/aura_engine/scheduling/domain.py b/src/aura_engine/scheduling/domain.py index 31487d01..481994e5 100644 --- a/src/aura_engine/scheduling/domain.py +++ b/src/aura_engine/scheduling/domain.py @@ -538,6 +538,7 @@ class PlayState: READY = "ready" PLAYING = "playing" DONE = "done" + FAILED = "failed" state: PlayStateType play_start: float diff --git a/src/aura_engine/scheduling/scheduler.py b/src/aura_engine/scheduling/scheduler.py index 645d683b..7082a8b4 100644 --- a/src/aura_engine/scheduling/scheduler.py +++ b/src/aura_engine/scheduling/scheduler.py @@ -41,7 +41,7 @@ from aura_engine.base.utils import SimpleUtil as SU from aura_engine.control import EngineExecutor from aura_engine.core.channels import LoadSourceException from aura_engine.resources import ResourceClass, ResourceUtil -from aura_engine.scheduling.domain import PlaylistItem, Timeslot +from aura_engine.scheduling.domain import PlaylistItem, PlayState, Timeslot # # EngineExecutor Commands @@ -183,6 +183,7 @@ class PlayCommand(EngineExecutor): last_item: PlaylistItem = items[-1] if not last_item.play.is_ready(): msg = f"Items didn't reach 'ready' state during preloading (Items: {items_str})" + last_item.play.state = PlayState.PlayStateType.FAILED self.logger.warning(SU.red(msg)) def do_play(self, items: list[PlaylistItem]): @@ -195,15 +196,13 @@ class PlayCommand(EngineExecutor): items_str = ResourceUtil.get_items_string(items) self.logger.info(SU.cyan(f"=== play('{items_str}') ===")) last_item: PlaylistItem = items[-1] - if not last_item.play.is_ready(): - # Let 'em play anyway ... + while not last_item.play.is_ready(): msg = f"PLAY: Item(s) not yet ready to be played" f" (Items: {items_str})" self.logger.critical(SU.red(msg)) - now = SU.timestamp() - while not last_item.play.is_ready() and SU.timestamp() <= now + last_item.duration: - self.logger.info("PLAY: Wait a little bit until preloading is done ...") - time.sleep(2) - + if last_item.play.state == PlayState.PlayStateType.FAILED: + self.logger.info("PLAY: Preloading failed - skipping play") + return + time.sleep(2) self.engine.player.play(items[0], engine.Player.TransitionType.FADE) timetable_renderer: utils.TimetableRenderer = self.engine.scheduler.timetable_renderer self.logger.info(timetable_renderer.get_ascii_timeslots()) -- GitLab