diff --git a/src/auracast/multicast.py b/src/auracast/multicast.py index 5fe774c..9a00846 100644 --- a/src/auracast/multicast.py +++ b/src/auracast/multicast.py @@ -193,8 +193,8 @@ async def init_broadcast( ) + ( [ - # Broadcast Audio Immediate Rendering flag (type 0x09), zero-length value - le_audio.Metadata.Entry(tag = le_audio.Metadata.Tag.ASSISTED_LISTENING_STREAM, data=b"") + # Assisted Listening Stream tag expects a 1-octet value. Use 0x01 to indicate enabled. + le_audio.Metadata.Entry(tag = le_audio.Metadata.Tag.ASSISTED_LISTENING_STREAM, data=b"\x01") ] if global_config.assisted_listening_stream else [] diff --git a/src/auracast/server/multicast_frontend.py b/src/auracast/server/multicast_frontend.py index bececa8..e63fc25 100644 --- a/src/auracast/server/multicast_frontend.py +++ b/src/auracast/server/multicast_frontend.py @@ -103,17 +103,17 @@ st.title("Auracast Audio Mode Control") # Audio mode selection with persisted default # Note: backend persists 'USB' for any device: source (including AES67). We default to 'USB' in that case. -options = ["Webapp", "USB", "AES67", "Demo"] -saved_audio_mode = saved_settings.get("audio_mode", "Webapp") +options = ["Demo", "USB", "AES67", "Webapp"] +saved_audio_mode = saved_settings.get("audio_mode", "Demo") if saved_audio_mode not in options: # Map legacy/unknown modes to closest mapping = {"USB/Network": "USB", "Network": "AES67"} - saved_audio_mode = mapping.get(saved_audio_mode, "Webapp") + saved_audio_mode = mapping.get(saved_audio_mode, "Demo") audio_mode = st.selectbox( "Audio Mode", options, - index=options.index(saved_audio_mode) if saved_audio_mode in options else options.index("Webapp"), + index=options.index(saved_audio_mode) if saved_audio_mode in options else options.index("Demo"), help=( "Select the audio input source. Choose 'Webapp' for browser microphone, " "'USB' for a connected USB audio device (via PipeWire), 'AES67' for network RTP/AES67 sources, " @@ -138,6 +138,25 @@ if audio_mode == "Demo": index=0, help="Select the demo stream configuration." ) + # Stream password and flags (same as USB/AES67) + saved_pwd = saved_settings.get('stream_password', '') or '' + stream_passwort = st.text_input( + "Stream Passwort", + value=saved_pwd, + type=("password"), + help="Optional: Set a broadcast code to protect your stream. Leave empty for an open (uncoded) broadcast." + ) + col_flags1, col_flags2, col_placeholder = st.columns([1, 1, 2]) + with col_flags1: + assisted_listening = st.checkbox( + "Assistive listening", + value=bool(saved_settings.get('assisted_listening_stream', False)) + ) + with col_flags2: + immediate_rendering = st.checkbox( + "Immediate rendering", + value=bool(saved_settings.get('immediate_rendering', False)) + ) #st.info(f"Demo mode selected: {demo_selected} (Streams: {demo_stream_map[demo_selected]['streams']}, Rate: {demo_stream_map[demo_selected]['rate']} Hz)") # Start/Stop buttons for demo mode if 'demo_stream_started' not in st.session_state: @@ -171,6 +190,7 @@ if audio_mode == "Demo": for i in range(demo_cfg['streams']): cfg_cls, lang = lang_cfgs[i % len(lang_cfgs)] bigs1.append(cfg_cls( + code=(stream_passwort.strip() or None), audio_source=f'file:../testdata/wave_particle_5min_{lang}_{int(q["rate"]/1000)}kHz_mono.wav', iso_que_len=32, sampling_frequency=q['rate'], @@ -188,6 +208,8 @@ if audio_mode == "Demo": auracast_sampling_rate_hz=q['rate'], octets_per_frame=q['octets'], transport='', # is set in backend + assisted_listening_stream=assisted_listening, + immediate_rendering=immediate_rendering, bigs=bigs1 ) config2 = None @@ -196,6 +218,8 @@ if audio_mode == "Demo": auracast_sampling_rate_hz=q['rate'], octets_per_frame=q['octets'], transport='', # is set in backend + assisted_listening_stream=assisted_listening, + immediate_rendering=immediate_rendering, bigs=bigs2 ) # Call /init and /init2 diff --git a/src/auracast/server/multicast_server.py b/src/auracast/server/multicast_server.py index 217ff40..72e2a5b 100644 --- a/src/auracast/server/multicast_server.py +++ b/src/auracast/server/multicast_server.py @@ -26,7 +26,6 @@ from auracast.utils.sounddevice_utils import ( load_dotenv() # make sure pipewire sets latency -os.environ.setdefault("PULSE_LATENCY_MSEC", "3") STREAM_SETTINGS_FILE = os.path.join(os.path.dirname(__file__), 'stream_settings.json') # Raspberry Pi UART transports TRANSPORT1 = os.getenv('TRANSPORT1', 'serial:/dev/ttyAMA3,1000000,rtscts') # transport for raspberry pi gpio header @@ -111,8 +110,11 @@ async def initialize(conf: auracast_config.AuracastConfigGroup): usb_names, net_names = set(), set() if input_device_name in net_names: audio_mode_persist = 'AES67' + os.environ.setdefault("PULSE_LATENCY_MSEC", "6") else: audio_mode_persist = 'USB' + os.environ.setdefault("PULSE_LATENCY_MSEC", "3") + # Map device name to current index for use with sounddevice device_index = get_device_index_by_name(input_device_name) if input_device_name else None # Patch config to use index for sounddevice (but persist name) @@ -143,6 +145,7 @@ async def initialize(conf: auracast_config.AuracastConfigGroup): 'octets_per_frame': conf.octets_per_frame, 'immediate_rendering': getattr(conf, 'immediate_rendering', False), 'assisted_listening_stream': getattr(conf, 'assisted_listening_stream', False), + 'stream_password': (conf.bigs[0].code if conf.bigs and getattr(conf.bigs[0], 'code', None) else None), 'timestamp': datetime.utcnow().isoformat() }) global_config_group = conf @@ -252,6 +255,7 @@ async def _autostart_from_settings(): and initializes streaming. """ try: + settings = load_stream_settings() or {} audio_mode = settings.get('audio_mode') input_device_name = settings.get('input_device') @@ -262,8 +266,21 @@ async def _autostart_from_settings(): channel_names = settings.get('channel_names') or ["Broadcast0"] program_info = settings.get('program_info') or channel_names languages = settings.get('languages') or ["deu"] + stream_password = settings.get('stream_password') original_ts = settings.get('timestamp') + try: + usb_names = {d.get('name') for _, d in list_usb_pw_inputs(refresh=False)} + net_names = {d.get('name') for _, d in list_network_pw_inputs(refresh=False)} + except Exception: + usb_names, net_names = set(), set() + if input_device_name in net_names: + audio_mode_persist = 'AES67' + os.environ.setdefault("PULSE_LATENCY_MSEC", "6") + else: + audio_mode_persist = 'USB' + os.environ.setdefault("PULSE_LATENCY_MSEC", "3") + # Only auto-start device-based inputs; Webapp and Demo require external sources/UI if not input_device_name: return @@ -297,6 +314,7 @@ async def _autostart_from_settings(): # Build a minimal config based on saved fields bigs = [ auracast_config.AuracastBigConfig( + code=stream_password, name=channel_names[0] if channel_names else "Broadcast0", program_info=program_info[0] if isinstance(program_info, list) and program_info else program_info, language=languages[0] if languages else "deu",