diff --git a/src/aura_engine/core/channels.py b/src/aura_engine/core/channels.py index 469475eabc2c08ed48f85e3bd913fbdba9761f77..ee087b1c83814ebfd5ea2cba4163fae49995efd1 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 94906cc687066b8214896b0a8964e7f68cdbeab1..f10ef3a23a9a9f95328df78d4f5805029bb25034 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 31487d010fe8d8ee4a3cd37f40f29e4ab317371f..481994e511392f290d9fde888ddbc8df1cf6e31e 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 645d683b9e9878a04c3075261a4d5ae3bce197ce..7082a8b465fde68e02e8fc1fd108024e95535f87 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())