From 690d31559bcb94794bf140ec26400a27ac8eae27 Mon Sep 17 00:00:00 2001 From: pstruebi Date: Wed, 19 Nov 2025 09:34:56 +0100 Subject: [PATCH] fix demo autostart --- src/auracast/server/multicast_server.py | 174 +++++++++++++++++++++++- 1 file changed, 170 insertions(+), 4 deletions(-) diff --git a/src/auracast/server/multicast_server.py b/src/auracast/server/multicast_server.py index 17a4d02..8a2e89b 100644 --- a/src/auracast/server/multicast_server.py +++ b/src/auracast/server/multicast_server.py @@ -263,6 +263,7 @@ async def init_radio(transport: str, conf: auracast_config.AuracastConfigGroup, 'demo_total_streams': demo_count, 'demo_stream_type': demo_type, 'is_streaming': auto_started, + 'demo_sources': [str(b.audio_source) for b in conf.bigs if isinstance(b.audio_source, str) and b.audio_source.startswith('file:')], } return mc, persisted except HTTPException: @@ -332,6 +333,8 @@ async def _autostart_from_settings(): settings1 = load_stream_settings() or {} settings2 = load_stream_settings2() or {} + log.info("[AUTOSTART] Starting autostart check: primary_ts=%s secondary_ts=%s", settings1.get('timestamp'), settings2.get('timestamp')) + async def do_primary(): global multicaster1, global_config_group settings = settings1 @@ -350,14 +353,96 @@ async def _autostart_from_settings(): original_ts = settings.get('timestamp') previously_streaming = bool(settings.get('is_streaming')) - if not previously_streaming or not input_device_name or rate is None or octets is None: + log.info( + "[AUTOSTART][PRIMARY] loaded settings: previously_streaming=%s audio_mode=%s rate=%s octets=%s pres_delay=%s rtn=%s immediate_rendering=%s assisted_listening_stream=%s demo_sources=%s", + previously_streaming, + audio_mode, + rate, + octets, + pres_delay, + saved_rtn, + immediate_rendering, + assisted_listening_stream, + (settings.get('demo_sources') or []), + ) + + if not previously_streaming: + log.info("[AUTOSTART][PRIMARY] Skipping autostart: is_streaming flag was False in persisted settings") + return + if audio_mode == 'Demo': + demo_sources = settings.get('demo_sources') or [] + if not demo_sources or rate is None or octets is None: + log.warning( + "[AUTOSTART][PRIMARY] Demo autostart aborted: demo_sources_present=%s rate=%s octets=%s", + bool(demo_sources), + rate, + octets, + ) + return + bigs = [] + for i, src in enumerate(demo_sources): + name = channel_names[i] if i < len(channel_names) else f"Broadcast{i}" + pinfo = program_info[i] if isinstance(program_info, list) and i < len(program_info) else (program_info[0] if isinstance(program_info, list) and program_info else program_info) + lang = languages[i] if i < len(languages) else (languages[0] if languages else "deu") + bigs.append( + auracast_config.AuracastBigConfig( + code=stream_password, + name=name, + program_info=pinfo, + language=lang, + audio_source=src, + iso_que_len=1, + sampling_frequency=rate, + octets_per_frame=octets, + ) + ) + log.info( + "[AUTOSTART][PRIMARY] Building demo config for %d streams, rate=%s, octets=%s", + len(bigs), + rate, + octets, + ) + + conf = auracast_config.AuracastConfigGroup( + auracast_sampling_rate_hz=rate, + octets_per_frame=octets, + transport=TRANSPORT1, + immediate_rendering=immediate_rendering, + assisted_listening_stream=assisted_listening_stream, + presentation_delay_us=pres_delay if pres_delay is not None else 40000, + bigs=bigs, + ) + conf.qos_config = auracast_config.AuracastQoSConfig( + iso_int_multiple_10ms=1, + number_of_retransmissions=int(saved_rtn) if saved_rtn is not None else 1, + max_transport_latency_ms=(int(saved_rtn) * 10 + 3) if saved_rtn is not None else 13, + ) + log.info("[AUTOSTART][PRIMARY] Scheduling demo init_radio in 2s") + await asyncio.sleep(2) + async with _stream_lock: + log.info("[AUTOSTART][PRIMARY] Calling init_radio for demo autostart") + mc, persisted = await init_radio(TRANSPORT1, conf, multicaster1) + multicaster1 = mc + global_config_group = conf + save_settings(persisted, secondary=False) + log.info("[AUTOSTART][PRIMARY] Demo autostart completed; settings persisted with is_streaming=%s", persisted.get('is_streaming')) + return + if not input_device_name or rate is None or octets is None: + log.info( + "[AUTOSTART][PRIMARY] Skipping device-based autostart: input_device=%s rate=%s octets=%s", + input_device_name, + rate, + octets, + ) return current = await _status_primary() if current.get('is_streaming'): + log.info("[AUTOSTART][PRIMARY] Skipping device-based autostart: stream already running") return while True: current = await _status_primary() if current.get('is_streaming'): + log.info("[AUTOSTART][PRIMARY] Aborting wait loop: stream started externally") return current_settings = load_stream_settings() or {} if current_settings.get('timestamp') != original_ts: @@ -365,11 +450,13 @@ async def _autostart_from_settings(): current_settings.get('input_device') != input_device_name or current_settings.get('audio_mode') != audio_mode ): + log.info("[AUTOSTART][PRIMARY] Aborting wait loop: settings changed (audio_mode/input_device)") return usb = [d for _, d in get_alsa_usb_inputs()] net = [d for _, d in get_network_pw_inputs()] names = {d.get('name') for d in usb} | {d.get('name') for d in net} if input_device_name in names: + log.info("[AUTOSTART][PRIMARY] Device '%s' detected, starting autostart", input_device_name) bigs = [ auracast_config.AuracastBigConfig( code=stream_password, @@ -396,12 +483,15 @@ async def _autostart_from_settings(): number_of_retransmissions=int(saved_rtn), max_transport_latency_ms=int(saved_rtn) * 10 + 3, ) + log.info("[AUTOSTART][PRIMARY] Scheduling device init_radio in 2s") await asyncio.sleep(2) async with _stream_lock: + log.info("[AUTOSTART][PRIMARY] Calling init_radio for device autostart") mc, persisted = await init_radio(TRANSPORT1, conf, multicaster1) multicaster1 = mc global_config_group = conf save_settings(persisted, secondary=False) + log.info("[AUTOSTART][PRIMARY] Device autostart completed; settings persisted with is_streaming=%s", persisted.get('is_streaming')) return await asyncio.sleep(2) @@ -422,12 +512,83 @@ async def _autostart_from_settings(): stream_password = settings.get('stream_password') original_ts = settings.get('timestamp') previously_streaming = bool(settings.get('is_streaming')) - - if not previously_streaming or not input_device_name or rate is None or octets is None: + log.info( + "[AUTOSTART][SECONDARY] loaded settings: previously_streaming=%s audio_mode=%s rate=%s octets=%s pres_delay=%s rtn=%s immediate_rendering=%s assisted_listening_stream=%s demo_sources=%s", + previously_streaming, + audio_mode, + rate, + octets, + pres_delay, + saved_rtn, + immediate_rendering, + assisted_listening_stream, + (settings.get('demo_sources') or []), + ) + if not previously_streaming: + log.info("[AUTOSTART][SECONDARY] Skipping autostart: is_streaming flag was False in persisted settings") + return + if audio_mode == 'Demo': + demo_sources = settings.get('demo_sources') or [] + if not demo_sources or rate is None or octets is None: + log.warning( + "[AUTOSTART][SECONDARY] Demo autostart aborted: demo_sources_present=%s rate=%s octets=%s", + bool(demo_sources), + rate, + octets, + ) + return + bigs = [] + for i, src in enumerate(demo_sources): + name = channel_names[i] if i < len(channel_names) else f"Broadcast{i}" + pinfo = program_info[i] if isinstance(program_info, list) and i < len(program_info) else (program_info[0] if isinstance(program_info, list) and program_info else program_info) + lang = languages[i] if i < len(languages) else (languages[0] if languages else "deu") + bigs.append( + auracast_config.AuracastBigConfig( + code=stream_password, + name=name, + program_info=pinfo, + language=lang, + audio_source=src, + iso_que_len=1, + sampling_frequency=rate, + octets_per_frame=octets, + ) + ) + conf = auracast_config.AuracastConfigGroup( + auracast_sampling_rate_hz=rate, + octets_per_frame=octets, + transport=TRANSPORT2, + immediate_rendering=immediate_rendering, + assisted_listening_stream=assisted_listening_stream, + presentation_delay_us=pres_delay if pres_delay is not None else 40000, + bigs=bigs, + ) + conf.qos_config = auracast_config.AuracastQoSConfig( + iso_int_multiple_10ms=1, + number_of_retransmissions=int(saved_rtn) if saved_rtn is not None else 1, + max_transport_latency_ms=(int(saved_rtn) * 10 + 3) if saved_rtn is not None else 13, + ) + log.info("[AUTOSTART][SECONDARY] Scheduling demo init_radio in 2s") + await asyncio.sleep(2) + async with _stream_lock: + log.info("[AUTOSTART][SECONDARY] Calling init_radio for demo autostart") + mc, persisted = await init_radio(TRANSPORT2, conf, multicaster2) + multicaster2 = mc + save_settings(persisted, secondary=True) + log.info("[AUTOSTART][SECONDARY] Demo autostart completed; settings persisted with is_streaming=%s", persisted.get('is_streaming')) + return + if not input_device_name or rate is None or octets is None: + log.info( + "[AUTOSTART][SECONDARY] Skipping device-based autostart: input_device=%s rate=%s octets=%s", + input_device_name, + rate, + octets, + ) return if multicaster2 is not None: try: if multicaster2.get_status().get('is_streaming'): + log.info("[AUTOSTART][SECONDARY] Skipping device-based autostart: stream already running") return except Exception: pass @@ -435,6 +596,7 @@ async def _autostart_from_settings(): if multicaster2 is not None: try: if multicaster2.get_status().get('is_streaming'): + log.info("[AUTOSTART][SECONDARY] Aborting wait loop: stream started externally") return except Exception: pass @@ -475,11 +637,14 @@ async def _autostart_from_settings(): number_of_retransmissions=int(saved_rtn), max_transport_latency_ms=int(saved_rtn) * 10 + 3, ) + log.info("[AUTOSTART][SECONDARY] Scheduling device init_radio in 2s") await asyncio.sleep(2) async with _stream_lock: + log.info("[AUTOSTART][SECONDARY] Calling init_radio for device autostart") mc, persisted = await init_radio(TRANSPORT2, conf, multicaster2) multicaster2 = mc save_settings(persisted, secondary=True) + log.info("[AUTOSTART][SECONDARY] Device autostart completed; settings persisted with is_streaming=%s", persisted.get('is_streaming')) return await asyncio.sleep(2) @@ -489,11 +654,12 @@ async def _autostart_from_settings(): @app.on_event("startup") async def _startup_autostart_event(): # Spawn the autostart task without blocking startup - log.info("Refreshing PipeWire device cache.") + log.info("[STARTUP] Auracast multicast server startup: initializing settings cache, I2C, and PipeWire cache") # Hydrate settings cache once to avoid disk I/O during /status _init_settings_cache_from_disk() await _init_i2c_on_startup() refresh_pw_cache() + log.info("[STARTUP] Scheduling autostart task") asyncio.create_task(_autostart_from_settings()) @app.get("/audio_inputs_pw_usb")