feature/analog_input (#12)
Co-authored-by: Paul Obernesser <paul.obernesser@inncubator.at> Reviewed-on: https://gitea.pstruebi.xyz/auracaster/bumble-auracast/pulls/12
This commit was merged in pull request #12.
This commit is contained in:
@@ -8,6 +8,8 @@ import requests
|
||||
from dotenv import load_dotenv
|
||||
import streamlit as st
|
||||
|
||||
from auracast.utils.read_temp import read_case_temp, read_cpu_temp
|
||||
|
||||
from auracast import auracast_config
|
||||
from auracast.utils.frontend_auth import (
|
||||
is_pw_disabled,
|
||||
@@ -100,6 +102,10 @@ except Exception:
|
||||
# Define is_streaming early from the fetched status for use throughout the UI
|
||||
is_streaming = bool(saved_settings.get("is_streaming", False))
|
||||
|
||||
# Extract secondary status, if provided by the backend /status endpoint.
|
||||
secondary_status = saved_settings.get("secondary") or {}
|
||||
secondary_is_streaming = bool(saved_settings.get("secondary_is_streaming", secondary_status.get("is_streaming", False)))
|
||||
|
||||
st.title("Auracast Audio Mode Control")
|
||||
|
||||
def render_stream_controls(status_streaming: bool, start_label: str, stop_label: str, mode_label: str):
|
||||
@@ -119,9 +125,10 @@ def render_stream_controls(status_streaming: bool, start_label: str, stop_label:
|
||||
# Audio mode selection with persisted default
|
||||
# Note: backend persists 'USB' for any device:<name> source (including AES67). We default to 'USB' in that case.
|
||||
options = [
|
||||
"Demo",
|
||||
"USB",
|
||||
"Network",
|
||||
"Demo",
|
||||
"Analog",
|
||||
"USB",
|
||||
"Network",
|
||||
]
|
||||
saved_audio_mode = saved_settings.get("audio_mode", "Demo")
|
||||
if saved_audio_mode not in options:
|
||||
@@ -153,7 +160,12 @@ if isinstance(backend_mode_raw, str):
|
||||
elif backend_mode_raw in options:
|
||||
backend_mode_mapped = backend_mode_raw
|
||||
|
||||
running_mode = backend_mode_mapped if (is_streaming and backend_mode_mapped) else audio_mode
|
||||
# When Analog is selected in the UI we always show it as such, even though the
|
||||
# backend currently persists USB for all device sources.
|
||||
if audio_mode == "Analog":
|
||||
running_mode = "Analog"
|
||||
else:
|
||||
running_mode = backend_mode_mapped if (is_streaming and backend_mode_mapped) else audio_mode
|
||||
|
||||
is_started = False
|
||||
is_stopped = False
|
||||
@@ -338,111 +350,103 @@ if audio_mode == "Demo":
|
||||
|
||||
quality = None # Not used in demo mode
|
||||
else:
|
||||
# Stream quality selection (now enabled)
|
||||
quality_options = list(QUALITY_MAP.keys())
|
||||
default_quality = "Medium (24kHz)" if "Medium (24kHz)" in quality_options else quality_options[0]
|
||||
quality = st.selectbox(
|
||||
"Stream Quality (Sampling Rate)",
|
||||
quality_options,
|
||||
index=quality_options.index(default_quality),
|
||||
help="Select the audio sampling rate for the stream. Lower rates may improve compatibility."
|
||||
)
|
||||
# --- Mode-specific configuration ---
|
||||
default_name = saved_settings.get('channel_names', ["Broadcast0"])[0]
|
||||
default_lang = saved_settings.get('languages', ["deu"])[0]
|
||||
default_input = saved_settings.get('input_device') or 'default'
|
||||
stream_name = st.text_input(
|
||||
"Channel Name",
|
||||
value=default_name,
|
||||
help="The primary name for your broadcast. Like the SSID of a WLAN, it identifies your stream for receivers."
|
||||
)
|
||||
raw_program_info = saved_settings.get('program_info', default_name)
|
||||
if isinstance(raw_program_info, list) and raw_program_info:
|
||||
default_program_info = raw_program_info[0]
|
||||
else:
|
||||
default_program_info = raw_program_info
|
||||
program_info = st.text_input(
|
||||
"Program Info",
|
||||
value=default_program_info,
|
||||
help="Additional details about the broadcast program, such as its content or purpose. Shown to receivers for more context."
|
||||
)
|
||||
language = st.text_input(
|
||||
"Language (ISO 639-3)",
|
||||
value=default_lang,
|
||||
help="Three-letter language code (e.g., 'eng' for English, 'deu' for German). Used by receivers to display the language of the stream. See: https://en.wikipedia.org/wiki/List_of_ISO_639-3_codes"
|
||||
)
|
||||
# Optional broadcast code for coded streams
|
||||
stream_passwort = st.text_input(
|
||||
"Stream Passwort",
|
||||
value="",
|
||||
type="password",
|
||||
help="Optional: Set a broadcast code to protect your stream. Leave empty for an open (uncoded) broadcast."
|
||||
)
|
||||
# Flags and QoS row (compact, four columns)
|
||||
col_flags1, col_flags2, col_pdelay, col_rtn = st.columns([1, 1, 0.7, 0.6], gap="small")
|
||||
with col_flags1:
|
||||
assisted_listening = st.checkbox(
|
||||
"Assistive listening",
|
||||
value=bool(saved_settings.get('assisted_listening_stream', False)),
|
||||
help="tells the receiver that this is an assistive listening stream"
|
||||
)
|
||||
with col_flags2:
|
||||
immediate_rendering = st.checkbox(
|
||||
"Immediate rendering",
|
||||
value=bool(saved_settings.get('immediate_rendering', False)),
|
||||
help="tells the receiver to ignore presentation delay and render immediately if possible."
|
||||
)
|
||||
# QoS/presentation controls inline with flags
|
||||
default_pdelay = int(saved_settings.get('presentation_delay_us', 40000) or 40000)
|
||||
with col_pdelay:
|
||||
default_pdelay_ms = max(10, min(200, default_pdelay // 1000))
|
||||
presentation_delay_ms = st.number_input(
|
||||
"Delay (ms)",
|
||||
min_value=10, max_value=200, step=5, value=default_pdelay_ms,
|
||||
help="Delay between capture and presentation for receivers."
|
||||
)
|
||||
default_rtn = int(saved_settings.get('rtn', 4) or 4)
|
||||
with col_rtn:
|
||||
rtn_options = [1,2,3,4]
|
||||
default_rtn_clamped = min(4, max(1, default_rtn))
|
||||
rtn = st.selectbox(
|
||||
"RTN", options=rtn_options, index=rtn_options.index(default_rtn_clamped),
|
||||
help="Number of ISO retransmissions (higher improves robustness at cost of airtime)."
|
||||
)
|
||||
|
||||
default_lang = saved_settings.get('languages', ["deu"])[0]
|
||||
|
||||
# Input device selection for USB or AES67 mode
|
||||
if audio_mode in ("USB", "Network"):
|
||||
# Per-mode configuration and controls
|
||||
input_device = None
|
||||
radio2_enabled = False
|
||||
radio1_cfg = None
|
||||
radio2_cfg = None
|
||||
|
||||
if audio_mode == "Analog":
|
||||
# --- Radio 1 controls ---
|
||||
st.subheader("Radio 1")
|
||||
|
||||
quality_options = list(QUALITY_MAP.keys())
|
||||
default_quality = "Medium (24kHz)" if "Medium (24kHz)" in quality_options else quality_options[0]
|
||||
quality1 = st.selectbox(
|
||||
"Stream Quality (Radio 1)",
|
||||
quality_options,
|
||||
index=quality_options.index(default_quality),
|
||||
help="Select the audio sampling rate for Radio 1."
|
||||
)
|
||||
|
||||
stream_passwort1 = st.text_input(
|
||||
"Stream Passwort (Radio 1)",
|
||||
value="",
|
||||
type="password",
|
||||
help="Optional: Set a broadcast code for Radio 1."
|
||||
)
|
||||
|
||||
col_r1_flags1, col_r1_flags2, col_r1_pdelay, col_r1_rtn = st.columns([1, 1, 0.7, 0.6], gap="small")
|
||||
with col_r1_flags1:
|
||||
assisted_listening1 = st.checkbox(
|
||||
"Assistive listening (R1)",
|
||||
value=bool(saved_settings.get('assisted_listening_stream', False)),
|
||||
help="tells the receiver that this is an assistive listening stream"
|
||||
)
|
||||
with col_r1_flags2:
|
||||
immediate_rendering1 = st.checkbox(
|
||||
"Immediate rendering (R1)",
|
||||
value=bool(saved_settings.get('immediate_rendering', False)),
|
||||
help="tells the receiver to ignore presentation delay and render immediately if possible."
|
||||
)
|
||||
default_pdelay = int(saved_settings.get('presentation_delay_us', 40000) or 40000)
|
||||
with col_r1_pdelay:
|
||||
default_pdelay_ms = max(10, min(200, default_pdelay // 1000))
|
||||
presentation_delay_ms1 = st.number_input(
|
||||
"Delay (ms, R1)",
|
||||
min_value=10, max_value=200, step=5, value=default_pdelay_ms,
|
||||
help="Delay between capture and presentation for Radio 1."
|
||||
)
|
||||
default_rtn = int(saved_settings.get('rtn', 4) or 4)
|
||||
with col_r1_rtn:
|
||||
rtn_options = [1,2,3,4]
|
||||
default_rtn_clamped = min(4, max(1, default_rtn))
|
||||
rtn1 = st.selectbox(
|
||||
"RTN (R1)", options=rtn_options, index=rtn_options.index(default_rtn_clamped),
|
||||
help="Number of ISO retransmissions for Radio 1."
|
||||
)
|
||||
|
||||
col_r1_name, col_r1_lang = st.columns([2, 1])
|
||||
with col_r1_name:
|
||||
stream_name1 = st.text_input(
|
||||
"Channel Name (Radio 1)",
|
||||
value=default_name,
|
||||
help="Name for the first analog radio (Radio 1)."
|
||||
)
|
||||
with col_r1_lang:
|
||||
language1 = st.text_input(
|
||||
"Language (ISO 639-3) (Radio 1)",
|
||||
value=default_lang,
|
||||
help="Language code for Radio 1."
|
||||
)
|
||||
program_info1 = st.text_input(
|
||||
"Program Info (Radio 1)",
|
||||
value=default_program_info,
|
||||
help="Program information for Radio 1."
|
||||
)
|
||||
|
||||
# Analog mode exposes only ALSA ch1/ch2 inputs.
|
||||
if not is_streaming:
|
||||
# Only query device lists when NOT streaming to avoid extra backend calls
|
||||
try:
|
||||
endpoint = "/audio_inputs_pw_usb" if audio_mode == "USB" else "/audio_inputs_pw_network"
|
||||
resp = requests.get(f"{BACKEND_URL}{endpoint}")
|
||||
resp = requests.get(f"{BACKEND_URL}/audio_inputs_pw_usb")
|
||||
device_list = resp.json().get('inputs', [])
|
||||
except Exception as e:
|
||||
st.error(f"Failed to fetch devices: {e}")
|
||||
device_list = []
|
||||
|
||||
# Display "name [id]" but use name as value
|
||||
input_options = [f"{d['name']} [{d['id']}]" for d in device_list]
|
||||
option_name_map = {f"{d['name']} [{d['id']}]": d['name'] for d in device_list}
|
||||
device_names = [d['name'] for d in device_list]
|
||||
analog_devices = [d for d in device_list if d.get('name') in ('ch1', 'ch2')]
|
||||
|
||||
# Determine default input by name (from persisted server state)
|
||||
default_input_name = saved_settings.get('input_device')
|
||||
if default_input_name not in device_names and device_names:
|
||||
default_input_name = device_names[0]
|
||||
default_input_label = None
|
||||
for label, name in option_name_map.items():
|
||||
if name == default_input_name:
|
||||
default_input_label = label
|
||||
break
|
||||
if not input_options:
|
||||
warn_text = (
|
||||
"No USB audio input devices found. Connect a USB input and click Refresh."
|
||||
if audio_mode == "USB" else
|
||||
"No AES67/Network inputs found."
|
||||
)
|
||||
st.warning(warn_text)
|
||||
if not analog_devices:
|
||||
st.warning("No Analog (ch1/ch2) ALSA inputs found. Check asound configuration.")
|
||||
if st.button("Refresh", disabled=is_streaming):
|
||||
try:
|
||||
r = requests.post(f"{BACKEND_URL}/refresh_audio_devices", timeout=8)
|
||||
@@ -451,16 +455,246 @@ else:
|
||||
except Exception as e:
|
||||
st.error(f"Failed to refresh devices: {e}")
|
||||
st.rerun()
|
||||
input_device = None
|
||||
analog_names = [d['name'] for d in analog_devices]
|
||||
else:
|
||||
analog_devices = []
|
||||
analog_names = []
|
||||
|
||||
if not is_streaming:
|
||||
if analog_names:
|
||||
default_r1_idx = 0
|
||||
input_device1 = st.selectbox(
|
||||
"Input Device (Radio 1)",
|
||||
analog_names,
|
||||
index=default_r1_idx,
|
||||
)
|
||||
else:
|
||||
col1, col2 = st.columns([3, 1], vertical_alignment="bottom")
|
||||
with col1:
|
||||
selected_option = st.selectbox(
|
||||
"Input Device",
|
||||
input_options,
|
||||
index=input_options.index(default_input_label) if default_input_label in input_options else 0
|
||||
input_device1 = None
|
||||
else:
|
||||
input_device1 = saved_settings.get('input_device')
|
||||
st.selectbox(
|
||||
"Input Device (Radio 1)",
|
||||
[input_device1 or "No device selected"],
|
||||
index=0,
|
||||
disabled=True,
|
||||
help="Stop the stream to change the input device."
|
||||
)
|
||||
|
||||
# --- Radio 2 controls ---
|
||||
st.subheader("Radio 2")
|
||||
# If the backend reports that the secondary radio is currently streaming,
|
||||
# initialize the checkbox to checked so the UI reflects the active state
|
||||
# when the frontend is loaded.
|
||||
radio2_enabled_default = secondary_is_streaming
|
||||
radio2_enabled = st.checkbox(
|
||||
"Enable Radio 2",
|
||||
value=radio2_enabled_default,
|
||||
help="Activate a second analog radio with its own quality and timing settings."
|
||||
)
|
||||
|
||||
if radio2_enabled:
|
||||
quality2 = st.selectbox(
|
||||
"Stream Quality (Radio 2)",
|
||||
quality_options,
|
||||
index=quality_options.index(default_quality),
|
||||
help="Select the audio sampling rate for Radio 2."
|
||||
)
|
||||
|
||||
stream_passwort2 = st.text_input(
|
||||
"Stream Passwort (Radio 2)",
|
||||
value="",
|
||||
type="password",
|
||||
help="Optional: Set a broadcast code for Radio 2."
|
||||
)
|
||||
|
||||
col_r2_flags1, col_r2_flags2, col_r2_pdelay, col_r2_rtn = st.columns([1, 1, 0.7, 0.6], gap="small")
|
||||
with col_r2_flags1:
|
||||
assisted_listening2 = st.checkbox(
|
||||
"Assistive listening (R2)",
|
||||
value=bool(saved_settings.get('assisted_listening_stream', False)),
|
||||
help="tells the receiver that this is an assistive listening stream"
|
||||
)
|
||||
with col_r2_flags2:
|
||||
immediate_rendering2 = st.checkbox(
|
||||
"Immediate rendering (R2)",
|
||||
value=bool(saved_settings.get('immediate_rendering', False)),
|
||||
help="tells the receiver to ignore presentation delay and render immediately if possible."
|
||||
)
|
||||
with col_r2_pdelay:
|
||||
presentation_delay_ms2 = st.number_input(
|
||||
"Delay (ms, R2)",
|
||||
min_value=10, max_value=200, step=5, value=default_pdelay_ms,
|
||||
help="Delay between capture and presentation for Radio 2."
|
||||
)
|
||||
with col_r2_rtn:
|
||||
rtn2 = st.selectbox(
|
||||
"RTN (R2)", options=rtn_options, index=rtn_options.index(default_rtn_clamped),
|
||||
help="Number of ISO retransmissions for Radio 2."
|
||||
)
|
||||
|
||||
col_r2_name, col_r2_lang = st.columns([2, 1])
|
||||
with col_r2_name:
|
||||
stream_name2 = st.text_input(
|
||||
"Channel Name (Radio 2)",
|
||||
value=f"{default_name}_2",
|
||||
help="Name for the second analog radio (Radio 2)."
|
||||
)
|
||||
with col_r2_lang:
|
||||
language2 = st.text_input(
|
||||
"Language (ISO 639-3) (Radio 2)",
|
||||
value=default_lang,
|
||||
help="Language code for Radio 2."
|
||||
)
|
||||
program_info2 = st.text_input(
|
||||
"Program Info (Radio 2)",
|
||||
value=default_program_info,
|
||||
help="Program information for Radio 2."
|
||||
)
|
||||
|
||||
if not is_streaming:
|
||||
if analog_names:
|
||||
default_r2_idx = 1 if len(analog_names) > 1 else 0
|
||||
input_device2 = st.selectbox(
|
||||
"Input Device (Radio 2)",
|
||||
analog_names,
|
||||
index=default_r2_idx,
|
||||
)
|
||||
with col2:
|
||||
else:
|
||||
input_device2 = None
|
||||
else:
|
||||
input_device2 = saved_settings.get('input_device')
|
||||
st.selectbox(
|
||||
"Input Device (Radio 2)",
|
||||
[input_device2 or "No device selected"],
|
||||
index=0,
|
||||
disabled=True,
|
||||
help="Stop the stream to change the input device."
|
||||
)
|
||||
|
||||
radio2_cfg = {
|
||||
'id': 1002,
|
||||
'name': stream_name2,
|
||||
'program_info': program_info2,
|
||||
'language': language2,
|
||||
'input_device': input_device2,
|
||||
'quality': quality2,
|
||||
'stream_passwort': stream_passwort2,
|
||||
'assisted_listening': assisted_listening2,
|
||||
'immediate_rendering': immediate_rendering2,
|
||||
'presentation_delay_ms': presentation_delay_ms2,
|
||||
'rtn': rtn2,
|
||||
}
|
||||
|
||||
radio1_cfg = {
|
||||
'id': 1001,
|
||||
'name': stream_name1,
|
||||
'program_info': program_info1,
|
||||
'language': language1,
|
||||
'input_device': input_device1,
|
||||
'quality': quality1,
|
||||
'stream_passwort': stream_passwort1,
|
||||
'assisted_listening': assisted_listening1,
|
||||
'immediate_rendering': immediate_rendering1,
|
||||
'presentation_delay_ms': presentation_delay_ms1,
|
||||
'rtn': rtn1,
|
||||
}
|
||||
|
||||
else:
|
||||
# USB/Network: single set of controls shared with the single channel
|
||||
quality_options = list(QUALITY_MAP.keys())
|
||||
default_quality = "Medium (24kHz)" if "Medium (24kHz)" in quality_options else quality_options[0]
|
||||
quality = st.selectbox(
|
||||
"Stream Quality (Sampling Rate)",
|
||||
quality_options,
|
||||
index=quality_options.index(default_quality),
|
||||
help="Select the audio sampling rate for the stream. Lower rates may improve compatibility."
|
||||
)
|
||||
|
||||
stream_passwort = st.text_input(
|
||||
"Stream Passwort",
|
||||
value="",
|
||||
type="password",
|
||||
help="Optional: Set a broadcast code to protect your stream. Leave empty for an open (uncoded) broadcast."
|
||||
)
|
||||
|
||||
col_flags1, col_flags2, col_pdelay, col_rtn = st.columns([1, 1, 0.7, 0.6], gap="small")
|
||||
with col_flags1:
|
||||
assisted_listening = st.checkbox(
|
||||
"Assistive listening",
|
||||
value=bool(saved_settings.get('assisted_listening_stream', False)),
|
||||
help="tells the receiver that this is an assistive listening stream"
|
||||
)
|
||||
with col_flags2:
|
||||
immediate_rendering = st.checkbox(
|
||||
"Immediate rendering",
|
||||
value=bool(saved_settings.get('immediate_rendering', False)),
|
||||
help="tells the receiver to ignore presentation delay and render immediately if possible."
|
||||
)
|
||||
default_pdelay = int(saved_settings.get('presentation_delay_us', 40000) or 40000)
|
||||
with col_pdelay:
|
||||
default_pdelay_ms = max(10, min(200, default_pdelay // 1000))
|
||||
presentation_delay_ms = st.number_input(
|
||||
"Delay (ms)",
|
||||
min_value=10, max_value=200, step=5, value=default_pdelay_ms,
|
||||
help="Delay between capture and presentation for receivers."
|
||||
)
|
||||
default_rtn = int(saved_settings.get('rtn', 4) or 4)
|
||||
with col_rtn:
|
||||
rtn_options = [1,2,3,4]
|
||||
default_rtn_clamped = min(4, max(1, default_rtn))
|
||||
rtn = st.selectbox(
|
||||
"RTN", options=rtn_options, index=rtn_options.index(default_rtn_clamped),
|
||||
help="Number of ISO retransmissions (higher improves robustness at cost of airtime)."
|
||||
)
|
||||
|
||||
stream_name = st.text_input(
|
||||
"Channel Name",
|
||||
value=default_name,
|
||||
help="The primary name for your broadcast. Like the SSID of a WLAN, it identifies your stream for receivers."
|
||||
)
|
||||
program_info = st.text_input(
|
||||
"Program Info",
|
||||
value=default_program_info,
|
||||
help="Additional details about the broadcast program, such as its content or purpose. Shown to receivers for more context."
|
||||
)
|
||||
language = st.text_input(
|
||||
"Language (ISO 639-3)",
|
||||
value=default_lang,
|
||||
help="Three-letter language code (e.g., 'eng' for English, 'deu' for German). Used by receivers to display the language of the stream. See: https://en.wikipedia.org/wiki/List_of_ISO_639-3_codes"
|
||||
)
|
||||
|
||||
if audio_mode in ("USB", "Network"):
|
||||
if not is_streaming:
|
||||
try:
|
||||
endpoint = "/audio_inputs_pw_usb" if audio_mode == "USB" else "/audio_inputs_pw_network"
|
||||
resp = requests.get(f"{BACKEND_URL}{endpoint}")
|
||||
device_list = resp.json().get('inputs', [])
|
||||
except Exception as e:
|
||||
st.error(f"Failed to fetch devices: {e}")
|
||||
device_list = []
|
||||
|
||||
if audio_mode == "USB":
|
||||
device_list = [d for d in device_list if d.get('name') not in ('ch1', 'ch2')]
|
||||
|
||||
input_options = [f"{d['name']} [{d['id']}]" for d in device_list]
|
||||
option_name_map = {f"{d['name']} [{d['id']}]": d['name'] for d in device_list}
|
||||
device_names = [d['name'] for d in device_list]
|
||||
|
||||
default_input_name = saved_settings.get('input_device')
|
||||
if default_input_name not in device_names and device_names:
|
||||
default_input_name = device_names[0]
|
||||
default_input_label = None
|
||||
for label, name in option_name_map.items():
|
||||
if name == default_input_name:
|
||||
default_input_label = label
|
||||
break
|
||||
if not input_options:
|
||||
warn_text = (
|
||||
"No USB audio input devices found. Connect a USB input and click Refresh."
|
||||
if audio_mode == "USB" else
|
||||
"No AES67/Network inputs found."
|
||||
)
|
||||
st.warning(warn_text)
|
||||
if st.button("Refresh", disabled=is_streaming):
|
||||
try:
|
||||
r = requests.post(f"{BACKEND_URL}/refresh_audio_devices", timeout=8)
|
||||
@@ -469,21 +703,38 @@ else:
|
||||
except Exception as e:
|
||||
st.error(f"Failed to refresh devices: {e}")
|
||||
st.rerun()
|
||||
# Send only the device name to backend
|
||||
input_device = option_name_map.get(selected_option)
|
||||
input_device = None
|
||||
else:
|
||||
col1, col2 = st.columns([3, 1], vertical_alignment="bottom")
|
||||
with col1:
|
||||
selected_option = st.selectbox(
|
||||
"Input Device",
|
||||
input_options,
|
||||
index=input_options.index(default_input_label) if default_input_label in input_options else 0
|
||||
)
|
||||
with col2:
|
||||
if st.button("Refresh", disabled=is_streaming):
|
||||
try:
|
||||
r = requests.post(f"{BACKEND_URL}/refresh_audio_devices", timeout=8)
|
||||
if not r.ok:
|
||||
st.error(f"Failed to refresh: {r.text}")
|
||||
except Exception as e:
|
||||
st.error(f"Failed to refresh devices: {e}")
|
||||
st.rerun()
|
||||
input_device = option_name_map.get(selected_option)
|
||||
else:
|
||||
input_device = saved_settings.get('input_device')
|
||||
current_label = input_device or "No device selected"
|
||||
st.selectbox(
|
||||
"Input Device",
|
||||
[current_label],
|
||||
index=0,
|
||||
disabled=True,
|
||||
help="Stop the stream to change the input device."
|
||||
)
|
||||
else:
|
||||
# When streaming, keep showing the current selection but lock editing.
|
||||
input_device = saved_settings.get('input_device')
|
||||
current_label = input_device or "No device selected"
|
||||
st.selectbox(
|
||||
"Input Device",
|
||||
[current_label],
|
||||
index=0,
|
||||
disabled=True,
|
||||
help="Stop the stream to change the input device."
|
||||
)
|
||||
else:
|
||||
input_device = None
|
||||
input_device = None
|
||||
|
||||
start_stream, stop_stream = render_stream_controls(is_streaming, "Start Auracast", "Stop Auracast", running_mode)
|
||||
|
||||
if stop_stream:
|
||||
@@ -499,48 +750,104 @@ else:
|
||||
if start_stream:
|
||||
# Always send stop to ensure backend is in a clean state, regardless of current status
|
||||
r = requests.post(f"{BACKEND_URL}/stop_audio").json()
|
||||
#if r['was_running']:
|
||||
# st.success("Stream Stopped!")
|
||||
|
||||
# Small pause lets backend fully release audio devices before re-init
|
||||
time.sleep(1)
|
||||
# Prepare config using the model (do NOT send qos_config, only relevant fields)
|
||||
q = QUALITY_MAP[quality]
|
||||
config = auracast_config.AuracastConfigGroup(
|
||||
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,
|
||||
presentation_delay_us=int(presentation_delay_ms * 1000),
|
||||
qos_config=auracast_config.AuracastQoSConfig(
|
||||
iso_int_multiple_10ms=1,
|
||||
number_of_retransmissions=int(rtn),
|
||||
max_transport_latency_ms=int(rtn)*10 + 3,
|
||||
),
|
||||
bigs = [
|
||||
auracast_config.AuracastBigConfig(
|
||||
code=(stream_passwort.strip() or None),
|
||||
name=stream_name,
|
||||
program_info=program_info,
|
||||
language=language,
|
||||
audio_source=(f"device:{input_device}"),
|
||||
input_format=(f"int16le,{q['rate']},1"),
|
||||
iso_que_len=1,
|
||||
sampling_frequency=q['rate'],
|
||||
octets_per_frame=q['octets'],
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
try:
|
||||
r = requests.post(f"{BACKEND_URL}/init", json=config.model_dump())
|
||||
if r.status_code == 200:
|
||||
is_started = True
|
||||
else:
|
||||
st.error(f"Failed to initialize: {r.text}")
|
||||
except Exception as e:
|
||||
st.error(f"Error: {e}")
|
||||
if audio_mode == "Analog":
|
||||
# Build separate configs per radio, each with its own quality and QoS parameters.
|
||||
is_started = False
|
||||
|
||||
def _build_group_from_radio(cfg: dict) -> auracast_config.AuracastConfigGroup | None:
|
||||
if not cfg or not cfg.get('input_device'):
|
||||
return None
|
||||
q = QUALITY_MAP[cfg['quality']]
|
||||
return auracast_config.AuracastConfigGroup(
|
||||
auracast_sampling_rate_hz=q['rate'],
|
||||
octets_per_frame=q['octets'],
|
||||
transport='', # is set in backend
|
||||
assisted_listening_stream=bool(cfg['assisted_listening']),
|
||||
immediate_rendering=bool(cfg['immediate_rendering']),
|
||||
presentation_delay_us=int(cfg['presentation_delay_ms'] * 1000),
|
||||
qos_config=auracast_config.AuracastQoSConfig(
|
||||
iso_int_multiple_10ms=1,
|
||||
number_of_retransmissions=int(cfg['rtn']),
|
||||
max_transport_latency_ms=int(cfg['rtn']) * 10 + 3,
|
||||
),
|
||||
bigs=[
|
||||
auracast_config.AuracastBigConfig(
|
||||
id=cfg.get('id', 123456),
|
||||
code=(cfg['stream_passwort'].strip() or None),
|
||||
name=cfg['name'],
|
||||
program_info=cfg['program_info'],
|
||||
language=cfg['language'],
|
||||
audio_source=f"device:{cfg['input_device']}",
|
||||
input_format=f"int16le,{q['rate']},1",
|
||||
iso_que_len=1,
|
||||
sampling_frequency=q['rate'],
|
||||
octets_per_frame=q['octets'],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
# Radio 1 (always active if a device is selected)
|
||||
config1 = _build_group_from_radio(radio1_cfg)
|
||||
# Radio 2 (optional)
|
||||
config2 = _build_group_from_radio(radio2_cfg) if radio2_enabled else None
|
||||
|
||||
try:
|
||||
if config1 is not None:
|
||||
r1 = requests.post(f"{BACKEND_URL}/init", json=config1.model_dump())
|
||||
if r1.status_code == 200:
|
||||
is_started = True
|
||||
else:
|
||||
st.error(f"Failed to initialize Radio 1: {r1.text}")
|
||||
else:
|
||||
st.error("Radio 1 has no valid input device configured.")
|
||||
|
||||
if config2 is not None:
|
||||
r2 = requests.post(f"{BACKEND_URL}/init2", json=config2.model_dump())
|
||||
if r2.status_code != 200:
|
||||
st.error(f"Failed to initialize Radio 2: {r2.text}")
|
||||
except Exception as e:
|
||||
st.error(f"Error while starting Analog radios: {e}")
|
||||
else:
|
||||
# USB/Network: single config as before, using shared controls
|
||||
q = QUALITY_MAP[quality]
|
||||
config = auracast_config.AuracastConfigGroup(
|
||||
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,
|
||||
presentation_delay_us=int(presentation_delay_ms * 1000),
|
||||
qos_config=auracast_config.AuracastQoSConfig(
|
||||
iso_int_multiple_10ms=1,
|
||||
number_of_retransmissions=int(rtn),
|
||||
max_transport_latency_ms=int(rtn)*10 + 3,
|
||||
),
|
||||
bigs=[
|
||||
auracast_config.AuracastBigConfig(
|
||||
code=(stream_passwort.strip() or None),
|
||||
name=stream_name,
|
||||
program_info=program_info,
|
||||
language=language,
|
||||
audio_source=(f"device:{input_device}"),
|
||||
input_format=(f"int16le,{q['rate']},1"),
|
||||
iso_que_len=1,
|
||||
sampling_frequency=q['rate'],
|
||||
octets_per_frame=q['octets'],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
try:
|
||||
r = requests.post(f"{BACKEND_URL}/init", json=config.model_dump())
|
||||
if r.status_code == 200:
|
||||
is_started = True
|
||||
else:
|
||||
st.error(f"Failed to initialize: {r.text}")
|
||||
except Exception as e:
|
||||
st.error(f"Error: {e}")
|
||||
|
||||
# Centralized rerun based on start/stop outcomes
|
||||
if is_started or is_stopped:
|
||||
@@ -563,6 +870,20 @@ if is_started or is_stopped:
|
||||
############################
|
||||
with st.expander("System control", expanded=False):
|
||||
|
||||
st.subheader("System temperatures")
|
||||
temp_col1, temp_col2, temp_col3 = st.columns([1, 1, 1])
|
||||
with temp_col1:
|
||||
refresh_temps = st.button("Refresh")
|
||||
try:
|
||||
case_temp = read_case_temp()
|
||||
cpu_temp = read_cpu_temp()
|
||||
with temp_col2:
|
||||
st.write(f"CPU: {cpu_temp} °C")
|
||||
with temp_col3:
|
||||
st.write(f"Case: {case_temp} °C")
|
||||
except Exception as e:
|
||||
st.warning(f"Could not read temperatures: {e}")
|
||||
|
||||
st.subheader("Change password")
|
||||
if is_pw_disabled():
|
||||
st.info("Frontend password protection is disabled via DISABLE_FRONTEND_PW.")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user